Guide to the Secure Configuration of Red Hat Enterprise Linux 10

with profile DRAFT - CIS Red Hat Enterprise Linux 10 Benchmark for Level 2 - Server
This is a draft profile for experimental purposes. It is based on the CIS RHEL 9 profile, because an equivalent policy for RHEL 10 didn't yet exist at time of the release.
This guide presents a catalog of security-relevant configuration settings for Red Hat Enterprise Linux 10. It is a rendering of content structured in the eXtensible Configuration Checklist Description Format (XCCDF) in order to support security automation. The SCAP content is is available in the scap-security-guide package which is developed at https://www.open-scap.org/security-policies/scap-security-guide.

Providing system administrators with such guidance informs them how to securely configure systems under their control in a variety of network roles. Policy makers and baseline creators can use this catalog of settings, with its associated references to higher-level security control catalogs, in order to assist them in security baseline creation. This guide is a catalog, not a checklist, and satisfaction of every item is not likely to be possible or sensible in many operational scenarios. However, the XCCDF format enables granular selection and adjustment of settings, and their association with OVAL and OCIL content provides an automated checking capability. Transformations of this document, and its associated automated checking content, are capable of providing baselines that meet a diverse set of policy objectives. Some example XCCDF Profiles, which are selections of items that form checklists and can be used as baselines, are available with this guide. They can be processed, in an automated fashion, with tools that support the Security Content Automation Protocol (SCAP). The DISA STIG, which provides required settings for US Department of Defense systems, is one example of a baseline created from this guidance.
Do not attempt to implement any of the settings in this guide without first testing them in a non-operational environment. The creators of this guidance assume no responsibility whatsoever for its use by other parties, and makes no guarantees, expressed or implied, about its quality, reliability, or any other characteristic.

Evaluation Characteristics

Evaluation targetip-172-31-7-17.eu-north-1.compute.internal
Benchmark URL#scap_org.open-scap_comp_ssg-rhel10-xccdf.xml
Benchmark IDxccdf_org.ssgproject.content_benchmark_RHEL-10
Benchmark version0.1.77
Profile IDxccdf_org.ssgproject.content_profile_cis
Started at2025-07-22T08:31:38+00:00
Finished at2025-07-22T08:32:07+00:00
Performed byec2-user
Test systemcpe:/a:redhat:openscap:1.4.2

CPE Platforms

  • cpe:/o:redhat:enterprise_linux:10

Addresses

  • IPv4  127.0.0.1
  • IPv4  172.31.7.17
  • IPv6  0:0:0:0:0:0:0:1
  • IPv6  fe80:0:0:0:c94:fcff:fe21:9631
  • MAC  00:00:00:00:00:00
  • MAC  0E:94:FC:21:96:31

Compliance and Scoring

The target system did not satisfy the conditions of 172 rules! Please review rule results and consider applying remediation.

Rule results

169 passed
172 failed
0 other

Severity of failed rules

4 other
11 low
154 medium
3 high

Score

Scoring systemScoreMaximumPercent
urn:xccdf:scoring:default71.187241100.000000
71.19%

Rule Overview

Group rules by:
TitleSeverityResult
Guide to the Secure Configuration of Red Hat Enterprise Linux 10 172x fail
System Settings 83x fail
Installing and Maintaining Software 14x fail
System and Software Integrity 4x fail
Software Integrity Checking 4x fail
Verify Integrity with AIDE 4x fail
Install AIDEmedium
fail
Build and Test AIDE Databasemedium
fail
Configure AIDE to Verify the Audit Toolsmedium
fail
Configure Periodic Execution of AIDEmedium
fail
System Cryptographic Policies
Configure System Cryptography Policyhigh
pass
Configure SSH to use System Crypto Policymedium
pass
Disk Partitioning 6x fail
Ensure /dev/shm is configuredlow
pass
Ensure /home Located On Separate Partitionlow
fail
Ensure /tmp Located On Separate Partitionlow
fail
Ensure /var Located On Separate Partitionlow
fail
Ensure /var/log Located On Separate Partitionlow
fail
Ensure /var/log/audit Located On Separate Partitionlow
fail
Ensure /var/tmp Located On Separate Partitionmedium
fail
GNOME Desktop Environment
Disable the GNOME3 Login User Listmedium
notapplicable
GNOME Media Settings
Disable GNOME3 Automountingmedium
notapplicable
Disable GNOME3 Automount Openingmedium
notapplicable
Disable GNOME3 Automount runninglow
notapplicable
Configure GNOME Screen Locking
Set GNOME3 Screensaver Inactivity Timeoutmedium
notapplicable
Set GNOME3 Screensaver Lock Delay After Activation Periodmedium
notapplicable
Ensure Users Cannot Change GNOME3 Screensaver Settingsmedium
notapplicable
Ensure Users Cannot Change GNOME3 Session Idle Settingsmedium
notapplicable
Remove the GDM Package Groupmedium
notapplicable
Make sure that the dconf databases are up-to-date with regards to respective keyfileshigh
notapplicable
Sudo 4x fail
Install sudo Packagemedium
pass
Ensure Only Users Logged In To Real tty Can Execute Sudo - sudo use_ptymedium
fail
Ensure Sudo Logfile Exists - sudo logfilelow
fail
Ensure Users Re-Authenticate for Privilege Escalation - sudomedium
fail
Require Re-Authentication When Using the sudo Commandmedium
fail
Updating Software
Ensure gpgcheck Enabled In Main dnf Configurationhigh
pass
Account and Access Control 27x fail
Warning Banners for System Accesses 2x fail
Enable GNOME3 Login Warning Bannermedium
notapplicable
Ensure Local Login Warning Banner Is Configured Properlymedium
fail
Ensure Remote Login Warning Banner Is Configured Properlymedium
fail
Ensure Message Of The Day Is Configured Properlymedium
pass
Verify Group Ownership of System Login Bannermedium
pass
Verify Group Ownership of System Login Banner for Remote Connectionsmedium
pass
Verify Group Ownership of Message of the Day Bannermedium
pass
Verify ownership of System Login Bannermedium
pass
Verify ownership of System Login Banner for Remote Connectionsmedium
pass
Verify ownership of Message of the Day Bannermedium
pass
Verify permissions on System Login Bannermedium
pass
Verify permissions on System Login Banner for Remote Connectionsmedium
pass
Verify permissions on Message of the Day Bannermedium
pass
Protect Accounts by Configuring PAM 13x fail
Set Lockouts for Failed Password Attempts 7x fail
Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth File.medium
fail
Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth File.medium
fail
Limit Password Reuse: password-authmedium
fail
Limit Password Reuse: system-authmedium
fail
Lock Accounts After Failed Password Attemptsmedium
fail
Configure the root Account for Failed Password Attemptsmedium
fail
Set Lockout Time for Failed Password Attemptsmedium
fail
Set Password Quality Requirements 6x fail
Set Password Quality Requirements with pam_pwquality 6x fail
Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary Wordsmedium
fail
Ensure PAM Enforces Password Requirements - Minimum Different Charactersmedium
fail
Ensure PAM Enforces Password Requirements - Enforce for root Usermedium
fail
Set Password Maximum Consecutive Repeating Charactersmedium
fail
Ensure PAM Enforces Password Requirements - Minimum Different Categoriesmedium
fail
Ensure PAM Enforces Password Requirements - Minimum Lengthmedium
fail
Set Password Hashing Algorithm
Set Password Hashing Algorithm in /etc/libuser.confmedium
notapplicable
Set Password Hashing Algorithm in /etc/login.defsmedium
pass
Set PAM''s Password Hashing Algorithm - password-authmedium
pass
Set PAM''s Password Hashing Algorithmmedium
pass
Install pam_pwquality Packagemedium
pass
Protect Accounts by Restricting Password-Based Login 7x fail
Set Account Expiration Following Inactivitymedium
fail
Set Password Expiration Parameters 2x fail
Set Password Maximum Agemedium
fail
Set Password Minimum Agemedium
fail
Set Existing Passwords Maximum Agemedium
pass
Set Existing Passwords Minimum Agemedium
pass
Set Existing Passwords Warning Agemedium
pass
Set existing passwords a period of inactivity before they been lockedmedium
pass
Verify Proper Storage and Existence of Password Hashes 1x fail
Verify All Account Password Hashes are Shadowedmedium
pass
Ensure all users last password change date is in the pastmedium
pass
All GIDs referenced in /etc/passwd must be defined in /etc/grouplow
pass
Prevent Login to Accounts With Empty Passwordhigh
fail
Ensure There Are No Accounts With Blank or Null Passwordshigh
pass
Verify No .forward Files Existmedium
pass
Verify No netrc Files Existmedium
pass
Restrict Root Logins 3x fail
Verify Only Root Has UID 0high
pass
Verify Root Has A Primary GID 0high
pass
Ensure the Group Used by pam_wheel.so Module Exists on System and is Emptymedium
fail
Ensure Authentication Required for Single User Modemedium
fail
Ensure that System Accounts Are Lockedmedium
pass
Ensure that System Accounts Do Not Run a Shell Upon Loginmedium
pass
Enforce Usage of pam_wheel with Group Parameter for su Authenticationmedium
fail
Ensure All Groups on the System Have Unique Group IDmedium
pass
Ensure All Groups on the System Have Unique Group Namesmedium
pass
Secure Session Configuration Files for Login Accounts 5x fail
Ensure that No Dangerous Directories Exist in Root's Path
Ensure that Root's Path Does Not Include World or Group-Writable Directoriesmedium
pass
Ensure that Root's Path Does Not Include Relative Paths or Null Directoriesunknown
pass
Ensure that Users Have Sensible Umask Values 3x fail
Ensure the Default Bash Umask is Set Correctlymedium
fail
Ensure the Default Umask is Set Correctly in login.defsmedium
fail
Ensure the Default Umask is Set Correctly in /etc/profilemedium
fail
Set Interactive Session Timeoutmedium
fail
User Initialization Files Must Be Group-Owned By The Primary Groupmedium
pass
User Initialization Files Must Not Run World-Writable Programsmedium
pass
User Initialization Files Must Be Owned By the Primary Usermedium
pass
All Interactive Users Home Directories Must Existmedium
pass
All Interactive User Home Directories Must Be Owned By The Primary Usermedium
pass
Ensure All User Initialization Files Have Mode 0740 Or Less Permissivemedium
fail
All Interactive User Home Directories Must Have mode 0750 Or Less Permissivemedium
pass
GRUB2 bootloader configuration 2x fail
Non-UEFI GRUB2 bootloader configuration 2x fail
Verify /boot/grub2/grub.cfg Group Ownershipmedium
pass
Verify /boot/grub2/user.cfg Group Ownershipmedium
pass
Verify /boot/grub2/grub.cfg User Ownershipmedium
pass
Verify /boot/grub2/user.cfg User Ownershipmedium
pass
Verify /boot/grub2/grub.cfg Permissionsmedium
fail
Verify /boot/grub2/user.cfg Permissionsmedium
pass
Set Boot Loader Password in grub2high
fail
Configure Syslog 3x fail
Ensure Proper Configuration of Log Files
Ensure Log Files Are Owned By Appropriate Groupmedium
pass
Ensure Log Files Are Owned By Appropriate Usermedium
pass
Ensure System Log Files Have Correct Permissionsmedium
pass
systemd-journald 3x fail
Install systemd-journal-remote Packagemedium
fail
Enable systemd-journald Servicemedium
pass
Ensure journald is configured to compress large log filesmedium
fail
Ensure journald is configured to write log files to persistent diskmedium
fail
Disable systemd-journal-remote Socketmedium
pass
Network Configuration and Firewalls 27x fail
firewalld 3x fail
Inspect and Activate Default firewalld Rules 1x fail
Install firewalld Packagemedium
fail
Verify firewalld Enabledmedium
notapplicable
Strengthen the Default Ruleset 2x fail
Configure Firewalld to Restrict Loopback Trafficmedium
fail
Configure Firewalld to Trust Loopback Trafficmedium
fail
IPv6 7x fail
Configure IPv6 Settings if Necessary 7x fail
Configure Accepting Router Advertisements on All IPv6 Interfacesmedium
fail
Disable Accepting ICMP Redirects for All IPv6 Interfacesmedium
fail
Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfacesmedium
fail
Disable Kernel Parameter for IPv6 Forwardingmedium
fail
Disable Accepting Router Advertisements on all IPv6 Interfaces by Defaultmedium
fail
Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfacesmedium
fail
Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces by Defaultmedium
fail
Kernel Parameters Which Affect Networking 15x fail
Network Related Kernel Runtime Parameters for Hosts and Routers 12x fail
Disable Accepting ICMP Redirects for All IPv4 Interfacesmedium
fail
Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfacesmedium
fail
Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfacesunknown
fail
Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfacesmedium
fail
Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfacesmedium
fail
Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfacesmedium
fail
Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces by Defaultmedium
pass
Enable Kernel Paremeter to Log Martian Packets on all IPv4 Interfaces by Defaultunknown
fail
Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces by Defaultmedium
fail
Configure Kernel Parameter for Accepting Secure Redirects By Defaultmedium
fail
Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfacesmedium
fail
Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfacesunknown
fail
Enable Kernel Parameter to Use TCP Syncookies on Network Interfacesmedium
fail
Network Parameters for Hosts Only 3x fail
Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfacesmedium
fail
Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces by Defaultmedium
fail
Disable Kernel Parameter for IP Forwarding on IPv4 Interfacesmedium
fail
nftables
Install nftables Packagemedium
pass
Verify nftables Service is Disabledmedium
notapplicable
Uncommon Network Protocols 2x fail
Disable SCTP Supportmedium
fail
Disable TIPC Supportlow
fail
Wireless Networking
Disable Wireless Through Software Configuration
Disable Bluetooth Servicemedium
pass
Deactivate Wireless Network Interfacesmedium
notapplicable
File Permissions and Masks 10x fail
Verify Permissions on Important Files and Directories
Verify Permissions and Ownership of Old Passwords Filemedium
pass
Verify Group Who Owns Backup group Filemedium
pass
Verify Group Who Owns Backup gshadow Filemedium
pass
Verify Group Who Owns Backup passwd Filemedium
pass
Verify User Who Owns Backup shadow Filemedium
pass
Verify Group Who Owns group Filemedium
pass
Verify Group Who Owns gshadow Filemedium
pass
Verify Group Who Owns passwd Filemedium
pass
Verify Group Who Owns shadow Filemedium
pass
Verify Group Who Owns /etc/shells Filemedium
pass
Verify User Who Owns Backup group Filemedium
pass
Verify User Who Owns Backup gshadow Filemedium
pass
Verify User Who Owns Backup passwd Filemedium
pass
Verify Group Who Owns Backup shadow Filemedium
pass
Verify User Who Owns group Filemedium
pass
Verify User Who Owns gshadow Filemedium
pass
Verify User Who Owns passwd Filemedium
pass
Verify User Who Owns shadow Filemedium
pass
Verify Who Owns /etc/shells Filemedium
pass
Verify Permissions on Backup group Filemedium
pass
Verify Permissions on Backup gshadow Filemedium
pass
Verify Permissions on Backup passwd Filemedium
pass
Verify Permissions on Backup shadow Filemedium
pass
Verify Permissions on group Filemedium
pass
Verify Permissions on gshadow Filemedium
pass
Verify Permissions on passwd Filemedium
pass
Verify Permissions on shadow Filemedium
pass
Verify Permissions on /etc/shells Filemedium
pass
Verify that All World-Writable Directories Have Sticky Bits Setmedium
pass
Ensure No World-Writable Files Existmedium
pass
Ensure All Files Are Owned by a Groupmedium
pass
Ensure All Files Are Owned by a Usermedium
pass
Restrict Dynamic Mounting and Unmounting of Filesystems 3x fail
Disable the Automountermedium
notapplicable
Disable Mounting of squashfslow
fail
Disable Mounting of udflow
fail
Disable Modprobe Loading of USB Storage Drivermedium
fail
Restrict Partition Mount Options 3x fail
Add nodev Option to /dev/shmmedium
fail
Add noexec Option to /dev/shmmedium
fail
Add nosuid Option to /dev/shmmedium
fail
Add nodev Option to /homeunknown
notapplicable
Add nosuid Option to /homemedium
notapplicable
Add nodev Option to /tmpmedium
notapplicable
Add noexec Option to /tmpmedium
notapplicable
Add nosuid Option to /tmpmedium
notapplicable
Add nodev Option to /var/log/auditmedium
notapplicable
Add noexec Option to /var/log/auditmedium
notapplicable
Add nosuid Option to /var/log/auditmedium
notapplicable
Add nodev Option to /var/logmedium
notapplicable
Add noexec Option to /var/logmedium
notapplicable
Add nosuid Option to /var/logmedium
notapplicable
Add nodev Option to /varmedium
notapplicable
Add nosuid Option to /varmedium
notapplicable
Add nodev Option to /var/tmpmedium
notapplicable
Add noexec Option to /var/tmpmedium
notapplicable
Add nosuid Option to /var/tmpmedium
notapplicable
Restrict Programs from Dangerous Execution Patterns 4x fail
Disable Core Dumps 2x fail
Disable core dump backtracesmedium
fail
Disable storing core dumpmedium
fail
Enable ExecShield 1x fail
Enable Randomized Layout of Virtual Address Spacemedium
fail
Restrict usage of ptrace to descendant processesmedium
fail
SELinux
Install libselinux Packagehigh
pass
Uninstall mcstrans Packagelow
pass
Uninstall setroubleshoot Packagelow
pass
Ensure SELinux Not Disabled in /etc/default/grubmedium
pass
Ensure SELinux is Not Disabledhigh
pass
Configure SELinux Policymedium
pass
Ensure SELinux State is Enforcinghigh
pass
Services 25x fail
Avahi Server
Disable Avahi Server if Possible
Disable Avahi Server Softwaremedium
notapplicable
Cron and At Daemons 8x fail
Restrict at and cron to Authorized Users if Necessary 2x fail
Ensure that /etc/at.deny does not existmedium
pass
Ensure that /etc/cron.allow existsmedium
fail
Ensure that /etc/cron.deny does not existmedium
fail
Verify Group Who Owns /etc/at.allow filemedium
pass
Verify Group Who Owns /etc/cron.allow filemedium
pass
Verify User Who Owns /etc/at.allow filemedium
pass
Verify User Who Owns /etc/cron.allow filemedium
pass
Verify Permissions on /etc/at.allow filemedium
pass
Verify Permissions on /etc/cron.allow filemedium
pass
Install the cron servicemedium
pass
Enable cron Servicemedium
pass
Verify Group Who Owns cron.dmedium
pass
Verify Group Who Owns cron.dailymedium
pass
Verify Group Who Owns cron.hourlymedium
pass
Verify Group Who Owns cron.monthlymedium
pass
Verify Group Who Owns cron.weeklymedium
pass
Verify Group Who Owns Crontabmedium
pass
Verify Owner on cron.dmedium
pass
Verify Owner on cron.dailymedium
pass
Verify Owner on cron.hourlymedium
pass
Verify Owner on cron.monthlymedium
pass
Verify Owner on cron.weeklymedium
pass
Verify Owner on crontabmedium
pass
Verify Permissions on cron.dmedium
fail
Verify Permissions on cron.dailymedium
fail
Verify Permissions on cron.hourlymedium
fail
Verify Permissions on cron.monthlymedium
fail
Verify Permissions on cron.weeklymedium
fail
Verify Permissions on crontabmedium
fail
DHCP
Disable DHCP Server
Uninstall kea Packagemedium
pass
DNS Server
Disable DNS Server
Uninstall bind Packagelow
pass
Uninstall dnsmasq Packagelow
pass
FTP Server
Disable vsftpd if Possible
Uninstall vsftpd Packagehigh
pass
Remove ftp Packagelow
pass
Web Server
Disable Apache if Possible
Uninstall httpd Packageunknown
pass
Disable NGINX if Possible
Uninstall nginx Packageunknown
pass
IMAP and POP3 Server
Disable Cyrus IMAP
Uninstall cyrus-imapd Packageunknown
pass
Disable Dovecot
Uninstall dovecot Packageunknown
pass
LDAP
Configure OpenLDAP Clients
Ensure LDAP client is not installedlow
pass
Mail Server Software
Configure SMTP For Mail Clients
Disable Postfix Network Listeningmedium
notapplicable
Ensure Mail Transfer Agent is not Listening on any non-loopback Addressmedium
pass
NFS and RPC
Disable All NFS Services if Possible
Disable Services Used Only by NFS
Disable rpcbind Servicelow
pass
Configure NFS Clients
Disable NFS Server Daemons
Disable Network File System (nfs)unknown
pass
Network Time Protocol
A remote time server for Chrony is configuredmedium
pass
Ensure that chronyd is running under chrony user accountmedium
pass
Obsolete Services
Telnet
Uninstall telnet-server Packagehigh
pass
Remove telnet Clientslow
pass
TFTP Server
Uninstall tftp-server Packagehigh
pass
Remove tftp Daemonlow
pass
Uninstall rsync Packagemedium
pass
Print Support
Disable the CUPS Serviceunknown
pass
Proxy Server
Disable Squid if Possible
Uninstall squid Packageunknown
pass
Samba(SMB) Microsoft Windows File Sharing Server
Disable Samba if Possible
Uninstall Samba Packageunknown
pass
SNMP Server
Disable SNMP Server if Possible
Uninstall net-snmp Packageunknown
pass
SSH Server 17x fail
Configure OpenSSH Server if Necessary 17x fail
Set SSH Client Alive Count Maxmedium
fail
Set SSH Client Alive Intervalmedium
fail
Disable Host-Based Authenticationmedium
fail
Disable SSH Access via Empty Passwordshigh
fail
Disable GSSAPI Authenticationmedium
fail
Disable SSH Support for .rhosts Filesmedium
fail
Disable SSH Root Loginmedium
fail
Do Not Allow SSH Environment Optionsmedium
fail
Enable PAMmedium
pass
Enable SSH Warning Bannermedium
fail
Limit Users' SSH Accessunknown
fail
Ensure SSH LoginGraceTime is configuredmedium
fail
Set SSH Daemon LogLevel to VERBOSEmedium
fail
Set SSH authentication attempt limitmedium
fail
Set SSH MaxSessions limitmedium
fail
Ensure SSH MaxStartups is configuredmedium
fail
Use Only Strong Key Exchange algorithmsmedium
fail
Use Only Strong MACsmedium
fail
Verify Group Who Owns SSH Server config filemedium
pass
Verify Group Ownership on SSH Server Private *_key Key Filesmedium
pass
Verify Group Ownership on SSH Server Public *.pub Key Filesmedium
pass
Verify Owner on SSH Server config filemedium
pass
Verify Ownership on SSH Server Private *_key Key Filesmedium
pass
Verify Ownership on SSH Server Public *.pub Key Filesmedium
pass
Verify Permissions on SSH Server config filemedium
pass
Verify Permissions on SSH Server Private *_key Key Filesmedium
pass
Verify Permissions on SSH Server Public *.pub Key Filesmedium
pass
X Window System
Disable X Windows
Disable Graphical Environment Startup By Setting Default Targetmedium
pass
System Accounting with auditd 64x fail
Configure auditd Rules for Comprehensive Auditing 57x fail
Record Events that Modify the System's Discretionary Access Controls 14x fail
Record Events that Modify the System's Discretionary Access Controls - chmodmedium
fail
Record Events that Modify the System's Discretionary Access Controls - chownmedium
fail
Record Events that Modify the System's Discretionary Access Controls - fchmodmedium
fail
Record Events that Modify the System's Discretionary Access Controls - fchmodatmedium
fail
Record Events that Modify the System's Discretionary Access Controls - fchmodat2medium
fail
Record Events that Modify the System's Discretionary Access Controls - fchownmedium
fail
Record Events that Modify the System's Discretionary Access Controls - fchownatmedium
fail
Record Events that Modify the System's Discretionary Access Controls - fremovexattrmedium
fail
Record Events that Modify the System's Discretionary Access Controls - fsetxattrmedium
fail
Record Events that Modify the System's Discretionary Access Controls - lchownmedium
fail
Record Events that Modify the System's Discretionary Access Controls - lremovexattrmedium
fail
Record Events that Modify the System's Discretionary Access Controls - lsetxattrmedium
fail
Record Events that Modify the System's Discretionary Access Controls - removexattrmedium
fail
Record Events that Modify the System's Discretionary Access Controls - setxattrmedium
fail
Record Execution Attempts to Run ACL Privileged Commands 2x fail
Record Any Attempts to Run chaclmedium
fail
Record Any Attempts to Run setfaclmedium
fail
Record Execution Attempts to Run SELinux Privileged Commands 1x fail
Record Any Attempts to Run chconmedium
fail
Record File Deletion Events by User 5x fail
Ensure auditd Collects File Deletion Events by User - renamemedium
fail
Ensure auditd Collects File Deletion Events by User - renameatmedium
fail
Ensure auditd Collects File Deletion Events by User - renameat2medium
fail
Ensure auditd Collects File Deletion Events by User - unlinkmedium
fail
Ensure auditd Collects File Deletion Events by User - unlinkatmedium
fail
Record Unauthorized Access Attempts Events to Files (unsuccessful) 5x fail
Record Unsuccessful Access Attempts to Files - creatmedium
fail
Record Unsuccessful Access Attempts to Files - ftruncatemedium
fail
Record Unsuccessful Access Attempts to Files - openmedium
fail
Record Unsuccessful Access Attempts to Files - openatmedium
fail
Record Unsuccessful Access Attempts to Files - truncatemedium
fail
Record Information on Kernel Modules Loading and Unloading 4x fail
Ensure auditd Collects Information on Kernel Module Unloading - delete_modulemedium
fail
Ensure auditd Collects Information on Kernel Module Loading and Unloading - finit_modulemedium
fail
Ensure auditd Collects Information on Kernel Module Loading - init_modulemedium
fail
Ensure auditd Collects Information on Kernel Module Loading and Unloading - query_modulemedium
fail
Record Attempts to Alter Logon and Logout Events - faillockmedium
fail
Record Attempts to Alter Logon and Logout Events - lastlogmedium
fail
Record Information on the Use of Privileged Commands 3x fail
Ensure auditd Collects Information on the Use of Privileged Commandsmedium
fail
Ensure auditd Collects Information on the Use of Privileged Commands - kmodmedium
fail
Ensure auditd Collects Information on the Use of Privileged Commands - usermodmedium
fail
Records Events that Modify Date and Time Information 4x fail
Record attempts to alter time through adjtimexmedium
fail
Record Attempts to Alter Time Through clock_settimemedium
fail
Record attempts to alter time through settimeofdaymedium
fail
Record Attempts to Alter the localtime Filemedium
fail
Make the auditd Configuration Immutablemedium
fail
Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)medium
fail
Record Events that Modify the System's Mandatory Access Controls in usr/sharemedium
fail
Ensure auditd Collects Information on Exporting to Media (successful)medium
fail
Record Events that Modify the System's Network Environmentmedium
fail
Record Events that Modify the System's Network Environmentmedium
fail
Record Attempts to Alter Process and Session Initiation Information btmpmedium
fail
Record Attempts to Alter Process and Session Initiation Information utmpmedium
fail
Record Attempts to Alter Process and Session Initiation Information wtmpmedium
fail
Record Events When Executables Are Run As Another Usermedium
fail
Ensure auditd Collects System Administrator Actionsmedium
fail
Record Events that Modify User/Group Information - /etc/groupmedium
fail
Record Events that Modify User/Group Information - /etc/gshadowmedium
fail
Record Events that Modify User/Group Information - /etc/security/opasswdmedium
fail
Record Events that Modify User/Group Information - /etc/passwdmedium
fail
Record Events that Modify User/Group Information - /etc/shadowmedium
fail
Record Attempts to perform maintenance activitiesmedium
fail
System Audit Logs Must Have Mode 0750 or Less Permissivemedium
pass
System Audit Logs Must Be Group Owned By Rootmedium
pass
Audit Configuration Files Must Be Owned By Group rootmedium
pass
Audit Configuration Files Must Be Owned By Rootmedium
pass
System Audit Logs Must Be Owned By Rootmedium
pass
Audit Configuration Files Permissions are 640 or More Restrictivemedium
pass
System Audit Logs Must Have Mode 0640 or Less Permissivemedium
pass
Configure auditd Data Retention 5x fail
Configure auditd Disk Error Action on Disk Errormedium
fail
Configure auditd Disk Full Action when Disk Space Is Fullmedium
fail
Configure auditd mail_acct Action on Low Disk Spacemedium
pass
Configure auditd admin_space_left Action on Low Disk Spacemedium
fail
Configure auditd Max Log File Sizemedium
pass
Configure auditd max_log_file_action Upon Reaching Maximum Log Sizemedium
fail
Configure auditd space_left Action on Low Disk Spacemedium
fail
System Accounting with auditd
Verify that audit tools are owned by group rootmedium
pass
Verify that audit tools are owned by rootmedium
pass
Verify that audit tools Have Mode 0755 or lessmedium
pass
Ensure the audit-libs package as a part of audit Subsystem is Installedmedium
pass
Ensure the audit Subsystem is Installedmedium
pass
Enable auditd Servicemedium
pass
Enable Auditing for Processes Which Start Prior to the Audit Daemonlow
fail
Extend Audit Backlog Limit for the Audit Daemonlow
fail

Result Details

Install AIDExccdf_org.ssgproject.content_rule_package_aide_installed mediumCCE-90477-1

Install AIDE

Rule IDxccdf_org.ssgproject.content_rule_package_aide_installed
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-package_aide_installed:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-90477-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis5.10.1.3
cobit5APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6
ism1034, 1288, 1341, 1417
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nistCM-6(a)
nist-csfDE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidssReq-11.5
os-srgSRG-OS-000445-GPOS-00199
anssiR76, R79
cis6.1.1
pcidss411.5.2
Description
The aide package can be installed with the following command:
$ sudo dnf install aide
Rationale
The AIDE package must be installed if it is to be available for integrity checking.

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if ! rpm -q --quiet "aide" ; then
    dnf install -y "aide"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90477-1
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_aide_installed

- name: Ensure aide is installed
  package:
    name: aide
    state: present
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90477-1
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_aide_installed

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
include install_aide

class install_aide {
  package { 'aide':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

package --add=aide


[[packages]]
name = "aide"
version = "*"

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

package install aide

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

dnf install aide
OVAL test results details

package aide is installed  oval:ssg-test_package_aide_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_aide_installed:obj:1 of type rpminfo_object
Name
aide
Build and Test AIDE Databasexccdf_org.ssgproject.content_rule_aide_build_database mediumCCE-86942-0

Build and Test AIDE Database

Rule IDxccdf_org.ssgproject.content_rule_aide_build_database
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-aide_build_database:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86942-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis5.10.1.3
cobit5APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nistCM-6(a)
nist-csfDE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidssReq-11.5
os-srgSRG-OS-000445-GPOS-00199
anssiR76, R79
cis6.1.1
pcidss411.5.2
Description
Run the following command to generate a new database:
$ sudo /usr/sbin/aide --init
By default, the database will be written to the file /var/lib/aide/aide.db.new.gz. Storing the database, the configuration file /etc/aide.conf, and the binary /usr/sbin/aide (or hashes of these files), in a secure location (such as on read-only media) provides additional assurance about their integrity. The newly-generated database can be installed as follows:
$ sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
To initiate a manual check, run the following command:
$ sudo /usr/sbin/aide --check
If this check produces any unexpected output, investigate.
Rationale
For AIDE to be effective, an initial database of "known-good" information about files must be captured and it should be able to be verified against the installed files.

# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if ! rpm -q --quiet "aide" ; then
    dnf install -y "aide"
fi

/usr/sbin/aide --init
/bin/cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86942-0
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Build and Test AIDE Database - Ensure AIDE Is Installed
  ansible.builtin.package:
    name: '{{ item }}'
    state: present
  with_items:
  - aide
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86942-0
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Build and Test AIDE Database - Build and Test AIDE Database
  ansible.builtin.command: /usr/sbin/aide --init
  changed_when: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86942-0
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Build and Test AIDE Database - Check Whether the Stock AIDE Database Exists
  ansible.builtin.stat:
    path: /var/lib/aide/aide.db.new.gz
  register: aide_database_stat
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86942-0
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Build and Test AIDE Database - Stage AIDE Database
  ansible.builtin.copy:
    src: /var/lib/aide/aide.db.new.gz
    dest: /var/lib/aide/aide.db.gz
    backup: true
    remote_src: true
  when:
  - '"kernel" in ansible_facts.packages'
  - (aide_database_stat.stat.exists is defined and aide_database_stat.stat.exists)
  tags:
  - CCE-86942-0
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

package aide is installed  oval:ssg-test_package_aide_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_aide_installed:obj:1 of type rpminfo_object
Name
aide

Testing existence of operational aide database file  oval:ssg-test_aide_operational_database_absolute_path:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_operational_database_absolute_path:obj:1 of type file_object
Filepath
Referenced variable has no values (oval:ssg-variable_aide_operational_database_absolute_path:var:1)
Configure AIDE to Verify the Audit Toolsxccdf_org.ssgproject.content_rule_aide_check_audit_tools mediumCCE-86441-3

Configure AIDE to Verify the Audit Tools

Rule IDxccdf_org.ssgproject.content_rule_aide_check_audit_tools
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-aide_check_audit_tools:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86441-3

References:
nistAU-9(3), AU-9(3).1
os-srgSRG-OS-000278-GPOS-00108
cis6.1.3
Description
The operating system file integrity tool must be configured to protect the integrity of the audit tools.
Rationale
Protecting the integrity of the tools used for auditing purposes is a critical step toward ensuring the integrity of audit information. Audit information includes all information (e.g., audit records, audit settings, and audit reports) needed to successfully audit information system activity. Audit tools include but are not limited to vendor-provided and open-source audit tools needed to successfully view and manipulate audit information system activity and records. Audit tools include custom queries and report generators. It is not uncommon for attackers to replace the audit tools or inject code into the existing tools to provide the capability to hide or erase system activity from the audit logs. To address this risk, audit tools must be cryptographically signed to provide the capability to identify when the audit tools have been modified, manipulated, or replaced. An example is a checksum hash of the file or files.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if ! rpm -q --quiet "aide" ; then
    dnf install -y "aide"
fi










if grep -i '^.*/usr/sbin/auditctl.*$' /etc/aide.conf; then
sed -i "s#.*/usr/sbin/auditctl.*#/usr/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
else
echo "/usr/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
fi

if grep -i '^.*/usr/sbin/auditd.*$' /etc/aide.conf; then
sed -i "s#.*/usr/sbin/auditd.*#/usr/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
else
echo "/usr/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
fi

if grep -i '^.*/usr/sbin/ausearch.*$' /etc/aide.conf; then
sed -i "s#.*/usr/sbin/ausearch.*#/usr/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
else
echo "/usr/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
fi

if grep -i '^.*/usr/sbin/aureport.*$' /etc/aide.conf; then
sed -i "s#.*/usr/sbin/aureport.*#/usr/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
else
echo "/usr/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
fi

if grep -i '^.*/usr/sbin/autrace.*$' /etc/aide.conf; then
sed -i "s#.*/usr/sbin/autrace.*#/usr/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
else
echo "/usr/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
fi

if grep -i '^.*/usr/sbin/augenrules.*$' /etc/aide.conf; then
sed -i "s#.*/usr/sbin/augenrules.*#/usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
else
echo "/usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
fi

if grep -i '^.*/usr/sbin/rsyslogd.*$' /etc/aide.conf; then
sed -i "s#.*/usr/sbin/rsyslogd.*#/usr/sbin/rsyslogd p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
else
echo "/usr/sbin/rsyslogd p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86441-3
  - NIST-800-53-AU-9(3)
  - NIST-800-53-AU-9(3).1
  - aide_check_audit_tools
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure AIDE to Verify the Audit Tools - Gather List of Packages
  tags:
  - CCE-86441-3
  - NIST-800-53-AU-9(3)
  - NIST-800-53-AU-9(3).1
  - aide_check_audit_tools
  - aide_check_audit_tools
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  ansible.builtin.package_facts:
    manager: auto
  when: '"kernel" in ansible_facts.packages'

- name: Ensure aide is installed
  package:
    name: '{{ item }}'
    state: present
  with_items:
  - aide
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86441-3
  - NIST-800-53-AU-9(3)
  - NIST-800-53-AU-9(3).1
  - aide_check_audit_tools
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set audit_tools fact
  set_fact:
    audit_tools:
    - /usr/sbin/auditctl
    - /usr/sbin/auditd
    - /usr/sbin/augenrules
    - /usr/sbin/aureport
    - /usr/sbin/ausearch
    - /usr/sbin/autrace
    - /usr/sbin/rsyslogd
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86441-3
  - NIST-800-53-AU-9(3)
  - NIST-800-53-AU-9(3).1
  - aide_check_audit_tools
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure existing AIDE configuration for audit tools are correct
  lineinfile:
    path: /etc/aide.conf
    regexp: ^{{ item }}\s
    line: '{{ item }} p+i+n+u+g+s+b+acl+xattrs+sha512'
  with_items: '{{ audit_tools }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86441-3
  - NIST-800-53-AU-9(3)
  - NIST-800-53-AU-9(3).1
  - aide_check_audit_tools
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure AIDE to properly protect audit tools
  lineinfile:
    path: /etc/aide.conf
    line: '{{ item }} p+i+n+u+g+s+b+acl+xattrs+sha512'
  with_items: '{{ audit_tools }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86441-3
  - NIST-800-53-AU-9(3)
  - NIST-800-53-AU-9(3).1
  - aide_check_audit_tools
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

package aide is installed  oval:ssg-test_package_aide_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_aide_installed:obj:1 of type rpminfo_object
Name
aide

auditctl is checked in /etc/aide.conf  oval:ssg-test_aide_verify_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_verify_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/aide.conf^\/usr\/sbin\/auditctl\s+([^\n]+)$1

auditd is checked in /etc/aide.conf  oval:ssg-test_aide_verify_auditd:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_verify_auditd:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/aide.conf^/usr/sbin/auditd\s+([^\n]+)$1

ausearch is checked in /etc/aide.conf  oval:ssg-test_aide_verify_ausearch:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_verify_ausearch:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/aide.conf^/usr/sbin/ausearch\s+([^\n]+)$1

aureport is checked in /etc/aide.conf  oval:ssg-test_aide_verify_aureport:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_verify_aureport:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/aide.conf^/usr/sbin/aureport\s+([^\n]+)$1

autrace is checked in /etc/aide.conf  oval:ssg-test_aide_verify_autrace:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_verify_autrace:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/aide.conf^/usr/sbin/autrace\s+([^\n]+)$1

rsyslogd is checked in /etc/aide.conf  oval:ssg-test_aide_verify_rsyslogd:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_verify_rsyslogd:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/aide.conf^/usr/sbin/rsyslogd\s+([^\n]+)$1

augenrules is checked in /etc/aide.conf  oval:ssg-test_aide_verify_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_verify_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/aide.conf^/usr/sbin/augenrules\s+([^\n]+)$1
Configure Periodic Execution of AIDExccdf_org.ssgproject.content_rule_aide_periodic_cron_checking mediumCCE-86738-2

Configure Periodic Execution of AIDE

Rule IDxccdf_org.ssgproject.content_rule_aide_periodic_cron_checking
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-aide_periodic_cron_checking:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86738-2

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis5.10.1.3
cobit5APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nistSI-7, SI-7(1), CM-6(a)
nist-csfDE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidssReq-11.5
os-srgSRG-OS-000363-GPOS-00150, SRG-OS-000446-GPOS-00200, SRG-OS-000447-GPOS-00201
anssiR76
cis6.1.2
pcidss411.5.2
Description
At a minimum, AIDE should be configured to run a weekly scan. To implement a daily execution of AIDE at 4:05am using cron, add the following line to /etc/crontab:
05 4 * * * root /usr/sbin/aide --check
To implement a weekly execution of AIDE at 4:05am using cron, add the following line to /etc/crontab:
05 4 * * 0 root /usr/sbin/aide --check
AIDE can be executed periodically through other means; this is merely one example. The usage of cron's special time codes, such as @daily and @weekly is acceptable.
Rationale
By default, AIDE does not install itself for periodic execution. Periodically running AIDE is necessary to reveal unexpected changes in installed files.

Unauthorized changes to the baseline configuration could make the system vulnerable to various attacks or allow unauthorized access to the operating system. Changes to operating system configurations can have unintended side effects, some of which may be relevant to security.

Detecting such changes and providing an automated response can help avoid unintended, negative consequences that could ultimately affect the security state of the operating system. The operating system's Information Management Officer (IMO)/Information System Security Officer (ISSO) and System Administrators (SAs) must be notified via email and/or monitoring system trap when there is an unauthorized modification of a configuration item.

# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if ! rpm -q --quiet "aide" ; then
    dnf install -y "aide"
fi

if ! grep -q "/usr/sbin/aide --check" /etc/crontab ; then
    echo "05 4 * * * root /usr/sbin/aide --check" >> /etc/crontab
else
    sed -i '\!^.* --check.*$!d' /etc/crontab
    echo "05 4 * * * root /usr/sbin/aide --check" >> /etc/crontab
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86738-2
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure AIDE is installed
  package:
    name: '{{ item }}'
    state: present
  with_items:
  - aide
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86738-2
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set cron package name - RedHat
  set_fact:
    cron_pkg_name: cronie
  when:
  - '"kernel" in ansible_facts.packages'
  - ansible_os_family == "RedHat" or ansible_os_family == "Suse"
  tags:
  - CCE-86738-2
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set cron package name - Debian
  set_fact:
    cron_pkg_name: cron
  when:
  - '"kernel" in ansible_facts.packages'
  - ansible_os_family == "Debian"
  tags:
  - CCE-86738-2
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Install cron
  package:
    name: '{{ cron_pkg_name }}'
    state: present
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86738-2
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure Periodic Execution of AIDE
  cron:
    name: run AIDE check
    minute: 5
    hour: 4
    weekday: 0
    user: root
    job: /usr/sbin/aide --check
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86738-2
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

package aide is installed  oval:ssg-test_package_aide_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_aide_installed:obj:1 of type rpminfo_object
Name
aide

run aide with cron  oval:ssg-test_aide_periodic_cron_checking:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_test_aide_periodic_cron_checking:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/crontab^(([0-9]*[\s]*[0-9]*[\s]*\*[\s]*\*[\s]*(\*|([0-7]|mon|tue|wed|thu|fri|sat|sun)|[0-7]-[0-7]))|@(hourly|daily|weekly))[\s]*root[\s]*\/usr\/sbin\/aide[\s]*\-\-check.*$1

run aide with cron  oval:ssg-test_aide_crond_checking:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_test_aide_crond_checking:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/cron.d^.*$^(([0-9]*[\s]*[0-9]*[\s]*\*[\s]*\*[\s]*(\*|([0-7]|mon|tue|wed|thu|fri|sat|sun)|[0-7]-[0-7]))|@(hourly|daily|weekly))[\s]*root[\s]*\/usr\/sbin\/aide[\s]*\-\-check.*$1

run aide with cron  oval:ssg-test_aide_var_cron_checking:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_var_cron_checking:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/var/spool/cron/root^(([0-9]*[\s]*[0-9]*[\s]*\*[\s]*\*[\s]*(\*|([0-7]|mon|tue|wed|thu|fri|sat|sun)|[0-7]-[0-7]))|@(hourly|daily|weekly))[\s]*(root)?[\s]*\/usr\/sbin\/aide[\s]*\-\-check.*$1

run aide with cron.(daily|weekly)  oval:ssg-test_aide_crontabs_checking:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_aide_crontabs_checking:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
^/etc/cron.(daily|weekly)$^.*$^[^#]*\/usr\/sbin\/aide\s+\-\-check\s*$1
Configure System Cryptography Policyxccdf_org.ssgproject.content_rule_configure_crypto_policy highCCE-89085-5

Configure System Cryptography Policy

Rule IDxccdf_org.ssgproject.content_rule_configure_crypto_policy
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-configure_crypto_policy:def:1
Time2025-07-22T08:31:38+00:00
Severityhigh
Identifiers:

CCE-89085-5

References:
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.312(e)(1), 164.312(e)(2)(ii)
ism1446
nerc-cipCIP-003-8 R4.2, CIP-007-3 R5.1, CIP-007-3 R7.1
nistAC-17(a), AC-17(2), CM-6(a), MA-4(6), SC-13, SC-12(2), SC-12(3)
osppFCS_COP.1(1), FCS_COP.1(2), FCS_COP.1(3), FCS_COP.1(4), FCS_CKM.1, FCS_CKM.2, FCS_TLSC_EXT.1
os-srgSRG-OS-000396-GPOS-00176, SRG-OS-000393-GPOS-00173, SRG-OS-000394-GPOS-00174
cis1.6.1
pcidss42.2.7, 2.2
Description
To configure the system cryptography policy to use ciphers only from the DEFAULT policy, run the following command:
$ sudo update-crypto-policies --set DEFAULT
         
The rule checks if settings for selected crypto policy are configured as expected. Configuration files in the /etc/crypto-policies/back-ends are either symlinks to correct files provided by Crypto-policies package or they are regular files in case crypto policy customizations are applied. Crypto policies may be customized by crypto policy modules, in which case it is delimited from the base policy using a colon.
Rationale
Centralized cryptographic policies simplify applying secure ciphers across an operating system and the applications that run on that operating system. Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to protect data.
Warnings
warning  The system needs to be rebooted for these changes to take effect.
warning  System Crypto Modules must be provided by a vendor that undergoes FIPS-140 certifications. FIPS-140 is applicable to all Federal agencies that use cryptographic-based security systems to protect sensitive information in computer and telecommunication systems (including voice systems) as defined in Section 5131 of the Information Technology Management Reform Act of 1996, Public Law 104-106. This standard shall be used in designing and implementing cryptographic modules that Federal departments and agencies operate or are operated for them under contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf To meet this, the system has to have cryptographic software provided by a vendor that has undergone this certification. This means providing documentation, test results, design information, and independent third party review by an accredited lab. While open source software is capable of meeting this, it does not meet FIPS-140 unless the vendor submits to this process.
OVAL test results details

check for crypto policy correctly configured in /etc/crypto-policies/config  oval:ssg-test_configure_crypto_policy:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/crypto-policies/configDEFAULT

check for crypto policy correctly configured in /etc/crypto-policies/state/current  oval:ssg-test_configure_crypto_policy_current:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/crypto-policies/state/currentDEFAULT

Check if update-crypto-policies has been run  oval:ssg-test_crypto_policies_updated:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-variable_crypto_policies_config_file_timestamp:var:11750255302

Check if /etc/crypto-policies/back-ends/nss.config exists  oval:ssg-test_crypto_policy_nss_config:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/etc/crypto-policies/back-ends/nss.configsymbolic link0042rwxrwxrwx 
Configure SSH to use System Crypto Policyxccdf_org.ssgproject.content_rule_configure_ssh_crypto_policy mediumCCE-88557-4

Configure SSH to use System Crypto Policy

Rule IDxccdf_org.ssgproject.content_rule_configure_ssh_crypto_policy
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-configure_ssh_crypto_policy:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88557-4

References:
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.312(e)(1), 164.312(e)(2)(ii)
nerc-cipCIP-003-8 R4.2, CIP-007-3 R5.1, CIP-007-3 R7.1
nistAC-17(a), AC-17(2), CM-6(a), MA-4(6), SC-13
osppFCS_SSH_EXT.1, FCS_SSHS_EXT.1, FCS_SSHC_EXT.1
pcidssReq-2.2
os-srgSRG-OS-000250-GPOS-00093
cis1.6.2
pcidss42.2.7, 2.2
Description
Crypto Policies provide a centralized control over crypto algorithms usage of many packages. SSH is supported by crypto policy, but the SSH configuration may be set up to ignore it. To check that Crypto Policies settings are configured correctly, ensure that the CRYPTO_POLICY variable is either commented or not set at all in the /etc/sysconfig/sshd.
Rationale
Overriding the system crypto policy makes the behavior of the SSH service violate expectations, and makes system configuration more fragmented.
OVAL test results details

Check that the SSH configuration mandates usage of system-wide crypto policies.  oval:ssg-test_configure_ssh_crypto_policy:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_configure_ssh_crypto_policy:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/sysconfig/sshd^\s*(?i)CRYPTO_POLICY\s*=.*$1
Ensure /dev/shm is configuredxccdf_org.ssgproject.content_rule_partition_for_dev_shm lowCCE-89532-6

Ensure /dev/shm is configured

Rule IDxccdf_org.ssgproject.content_rule_partition_for_dev_shm
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-partition_for_dev_shm:def:1
Time2025-07-22T08:31:38+00:00
Severitylow
Identifiers:

CCE-89532-6

References:
cis1.1.2.2.1
Description
The /dev/shm is a traditional shared memory concept. One program will create a memory portion, which other processes (if permitted) can access. If /dev/shm is not configured, tmpfs will be mounted to /dev/shm by systemd.
Rationale
Any user can upload and execute files inside the /dev/shm similar to the /tmp partition. Configuring /dev/shm allows an administrator to set the noexec option on the mount, making /dev/shm useless for an attacker to install executable code. It would also prevent an attacker from establishing a hardlink to a system setuid program and wait for it to be updated. Once the program was updated, the hardlink would be broken and the attacker would have his own copy of the program. If the program happened to have a security vulnerability, the attacker could continue to exploit the known flaw.
Warnings
warning  This rule does not have a remedation. It is expected that this will be managed by systemd and will be a tmpfs partition.
OVAL test results details

/dev/shm on own partition  oval:ssg-testdev_shm_partition:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMount pointDeviceUuidFs typeMount optionsMount optionsMount optionsMount optionsMount optionsTotal spaceSpace usedSpace left
not evaluated/dev/shmtmpfstmpfsrwseclabelnosuidnodevinode642108950210895
Ensure /home Located On Separate Partitionxccdf_org.ssgproject.content_rule_partition_for_home lowCCE-88231-6

Ensure /home Located On Separate Partition

Rule IDxccdf_org.ssgproject.content_rule_partition_for_home
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-partition_for_home:def:1
Time2025-07-22T08:31:38+00:00
Severitylow
Identifiers:

CCE-88231-6

References:
cis-csc12, 15, 8
cobit5APO13.01, DSS05.02
isa-62443-2013SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.13.1.1, A.13.2.1, A.14.1.3
nistCM-6(a), SC-5(2)
nist-csfPR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR28
cis1.1.2.3.1
Description
If user home directories will be stored locally, create a separate partition for /home at installation time (or migrate it later using LVM). If /home will be mounted from another system such as an NFS server, then creating a separate partition is not necessary at installation time, and the mountpoint can instead be configured later.
Rationale
Ensuring that /home is mounted on its own partition enables the setting of more restrictive mount options, and also helps ensure that users cannot trivially fill partitions used for log or audit data storage.

Complexity:low
Disruption:high
Reboot:false
Strategy:enable

part /home


[[customizations.filesystem]]
mountpoint = "/home"
size = 1073741824


logvol /home 1024
OVAL test results details

/home on own partition  oval:ssg-testhome_partition:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_mounthome_own_partition:obj:1 of type partition_object
Mount point
/home
Ensure /tmp Located On Separate Partitionxccdf_org.ssgproject.content_rule_partition_for_tmp lowCCE-89606-8

Ensure /tmp Located On Separate Partition

Rule IDxccdf_org.ssgproject.content_rule_partition_for_tmp
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-partition_for_tmp:def:1
Time2025-07-22T08:31:38+00:00
Severitylow
Identifiers:

CCE-89606-8

References:
cis-csc12, 15, 8
cobit5APO13.01, DSS05.02
isa-62443-2013SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.13.1.1, A.13.2.1, A.14.1.3
nistCM-6(a), SC-5(2)
nist-csfPR.PT-4
os-srgSRG-OS-000480-GPOS-00227
cis1.1.2.1.1
Description
The /tmp directory is a world-writable directory used for temporary file storage. Ensure it has its own partition or logical volume at installation time, or migrate it using LVM.
Rationale
The /tmp partition is used as temporary storage by many programs. Placing /tmp in its own partition enables the setting of more restrictive mount options, which can help protect programs which use it.

Complexity:low
Disruption:high
Reboot:false
Strategy:enable

part /tmp


[[customizations.filesystem]]
mountpoint = "/tmp"
size = 1073741824


logvol /tmp 1024
OVAL test results details

/tmp on own partition  oval:ssg-testtmp_partition:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_mounttmp_own_partition:obj:1 of type partition_object
Mount point
/tmp
Ensure /var Located On Separate Partitionxccdf_org.ssgproject.content_rule_partition_for_var lowCCE-89166-3

Ensure /var Located On Separate Partition

Rule IDxccdf_org.ssgproject.content_rule_partition_for_var
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-partition_for_var:def:1
Time2025-07-22T08:31:38+00:00
Severitylow
Identifiers:

CCE-89166-3

References:
cis-csc12, 15, 8
cobit5APO13.01, DSS05.02
isa-62443-2013SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.13.1.1, A.13.2.1, A.14.1.3
nistCM-6(a), SC-5(2)
nist-csfPR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR28
cis1.1.2.4.1
Description
The /var directory is used by daemons and other system services to store frequently-changing data. Ensure that /var has its own partition or logical volume at installation time, or migrate it using LVM.
Rationale
Ensuring that /var is mounted on its own partition enables the setting of more restrictive mount options. This helps protect system services such as daemons or other programs which use it. It is not uncommon for the /var directory to contain world-writable directories installed by other software packages.

Complexity:low
Disruption:high
Reboot:false
Strategy:enable

part /var


[[customizations.filesystem]]
mountpoint = "/var"
size = 3221225472


logvol /var 3072
OVAL test results details

/var on own partition  oval:ssg-testvar_partition:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_mountvar_own_partition:obj:1 of type partition_object
Mount point
/var
Ensure /var/log Located On Separate Partitionxccdf_org.ssgproject.content_rule_partition_for_var_log lowCCE-88355-3

Ensure /var/log Located On Separate Partition

Rule IDxccdf_org.ssgproject.content_rule_partition_for_var_log
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-partition_for_var_log:def:1
Time2025-07-22T08:31:38+00:00
Severitylow
Identifiers:

CCE-88355-3

References:
cis-csc1, 12, 14, 15, 16, 3, 5, 6, 8
cobit5APO11.04, APO13.01, BAI03.05, DSS05.02, DSS05.04, DSS05.07, MEA02.01
isa-62443-20094.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3
nerc-cipCIP-007-3 R6.5
nistCM-6(a), AU-4, SC-5(2)
nist-csfPR.PT-1, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR28
cis1.1.2.6.1
Description
System logs are stored in the /var/log directory. Ensure that /var/log has its own partition or logical volume at installation time, or migrate it using LVM.
Rationale
Placing /var/log in its own partition enables better separation between log files and other files in /var/.

Complexity:low
Disruption:high
Reboot:false
Strategy:enable

part /var/log


[[customizations.filesystem]]
mountpoint = "/var/log"
size = 1073741824


logvol /var/log 1024
OVAL test results details

/var/log on own partition  oval:ssg-testvar_log_partition:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_mountvar_log_own_partition:obj:1 of type partition_object
Mount point
/var/log
Ensure /var/log/audit Located On Separate Partitionxccdf_org.ssgproject.content_rule_partition_for_var_log_audit lowCCE-89211-7

Ensure /var/log/audit Located On Separate Partition

Rule IDxccdf_org.ssgproject.content_rule_partition_for_var_log_audit
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-partition_for_var_log_audit:def:1
Time2025-07-22T08:31:38+00:00
Severitylow
Identifiers:

CCE-89211-7

References:
cis-csc1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 8
cobit5APO11.04, APO13.01, BAI03.05, BAI04.04, DSS05.02, DSS05.04, DSS05.07, MEA02.01
hipaa164.312(a)(2)(ii)
isa-62443-20094.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.17.2.1
nerc-cipCIP-007-3 R6.5
nistCM-6(a), AU-4, SC-5(2)
nist-csfPR.DS-4, PR.PT-1, PR.PT-4
osppFMT_SMF_EXT.1
os-srgSRG-OS-000341-GPOS-00132, SRG-OS-000480-GPOS-00227
app-srg-ctrSRG-APP-000357-CTR-000800
anssiR71
cis1.1.2.7.1
Description
Audit logs are stored in the /var/log/audit directory. Ensure that /var/log/audit has its own partition or logical volume at installation time, or migrate it using LVM. Make absolutely certain that it is large enough to store all audit logs that will be created by the auditing daemon.
Rationale
Placing /var/log/audit in its own partition enables better separation between audit files and other files, and helps ensure that auditing cannot be halted due to the partition running out of space.

Complexity:low
Disruption:high
Reboot:false
Strategy:enable

part /var/log/audit


[[customizations.filesystem]]
mountpoint = "/var/log/audit"
size = 10737418240


logvol /var/log/audit 10240
OVAL test results details

/var/log/audit on own partition  oval:ssg-testvar_log_audit_partition:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_mountvar_log_audit_own_partition:obj:1 of type partition_object
Mount point
/var/log/audit
Ensure /var/tmp Located On Separate Partitionxccdf_org.ssgproject.content_rule_partition_for_var_tmp mediumCCE-87694-6

Ensure /var/tmp Located On Separate Partition

Rule IDxccdf_org.ssgproject.content_rule_partition_for_var_tmp
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-partition_for_var_tmp:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87694-6

References:
os-srgSRG-OS-000480-GPOS-00227
anssiR28
cis1.1.2.5.1
Description
The /var/tmp directory is a world-writable directory used for temporary file storage. Ensure it has its own partition or logical volume at installation time, or migrate it using LVM.
Rationale
The /var/tmp partition is used as temporary storage by many programs. Placing /var/tmp in its own partition enables the setting of more restrictive mount options, which can help protect programs which use it.

Complexity:low
Disruption:high
Reboot:false
Strategy:enable

part /var/tmp


[[customizations.filesystem]]
mountpoint = "/var/tmp"
size = 1073741824


logvol /var/tmp 1024
OVAL test results details

/var/tmp on own partition  oval:ssg-testvar_tmp_partition:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_mountvar_tmp_own_partition:obj:1 of type partition_object
Mount point
/var/tmp
Disable the GNOME3 Login User Listxccdf_org.ssgproject.content_rule_dconf_gnome_disable_user_list mediumCCE-87918-9

Disable the GNOME3 Login User List

Rule IDxccdf_org.ssgproject.content_rule_dconf_gnome_disable_user_list
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87918-9

References:
nistCM-6(a), AC-23
os-srgSRG-OS-000480-GPOS-00227
cis1.8.3
Description
In the default graphical environment, users logging directly into the system are greeted with a login screen that displays all known users. This functionality should be disabled by setting disable-user-list to true.

To disable, add or edit disable-user-list to /etc/dconf/db/distro.d/00-security-settings. For example:
[org/gnome/login-screen]
disable-user-list=true
Once the setting has been added, add a lock to /etc/dconf/db/distro.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/login-screen/disable-user-list
After the settings have been set, run dconf update.
Rationale
Leaving the user list enabled is a security risk since it allows anyone with physical access to the system to quickly enumerate known user accounts without logging in.
Disable GNOME3 Automountingxccdf_org.ssgproject.content_rule_dconf_gnome_disable_automount mediumCCE-89756-1

Disable GNOME3 Automounting

Rule IDxccdf_org.ssgproject.content_rule_dconf_gnome_disable_automount
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89756-1

References:
cis-csc12, 16
cobit5APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03
cui3.1.7
isa-62443-20094.3.3.2.2, 4.3.3.5.2, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.4, SR 1.5, SR 1.9, SR 2.1, SR 2.6
iso27001-2013A.11.2.6, A.13.1.1, A.13.2.1, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.AC-6
os-srgSRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227
cis1.8.6, 1.8.7
pcidss43.4.2, 3.4
Description
The system's default desktop environment, GNOME3, will mount devices and removable media (such as DVDs, CDs and USB flash drives) whenever they are inserted into the system. To disable automount within GNOME3, add or set automount to false in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/media-handling]
automount=false
Once the settings have been added, add a lock to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/media-handling/automount
After the settings have been set, run dconf update.
Rationale
Disabling automatic mounting in GNOME3 can prevent the introduction of malware via removable media. It will, however, also prevent desktop users from legitimate use of removable media.
Disable GNOME3 Automount Openingxccdf_org.ssgproject.content_rule_dconf_gnome_disable_automount_open mediumCCE-86628-5

Disable GNOME3 Automount Opening

Rule IDxccdf_org.ssgproject.content_rule_dconf_gnome_disable_automount_open
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86628-5

References:
cis-csc12, 16
cobit5APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03
cui3.1.7
isa-62443-20094.3.3.2.2, 4.3.3.5.2, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.4, SR 1.5, SR 1.9, SR 2.1, SR 2.6
iso27001-2013A.11.2.6, A.13.1.1, A.13.2.1, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.AC-6
os-srgSRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227
cis1.8.6, 1.8.7
pcidss43.4.2, 3.4
Description
The system's default desktop environment, GNOME3, will mount devices and removable media (such as DVDs, CDs and USB flash drives) whenever they are inserted into the system. To disable automount-open within GNOME3, add or set automount-open to false in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/media-handling]
automount-open=false
Once the settings have been added, add a lock to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/media-handling/automount-open
After the settings have been set, run dconf update.
Rationale
Automatically mounting file systems permits easy introduction of unknown devices, thereby facilitating malicious activity. Disabling automatic mounting in GNOME3 can prevent the introduction of malware via removable media. It will, however, also prevent desktop users from legitimate use of removable media.
Disable GNOME3 Automount runningxccdf_org.ssgproject.content_rule_dconf_gnome_disable_autorun lowCCE-87588-0

Disable GNOME3 Automount running

Rule IDxccdf_org.ssgproject.content_rule_dconf_gnome_disable_autorun
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitylow
Identifiers:

CCE-87588-0

References:
cis-csc12, 16
cobit5APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03
cui3.1.7
isa-62443-20094.3.3.2.2, 4.3.3.5.2, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.4, SR 1.5, SR 1.9, SR 2.1, SR 2.6
iso27001-2013A.11.2.6, A.13.1.1, A.13.2.1, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.AC-6
os-srgSRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227
cis1.8.8, 1.8.9
Description
The system's default desktop environment, GNOME3, will mount devices and removable media (such as DVDs, CDs and USB flash drives) whenever they are inserted into the system. To disable autorun-never within GNOME3, add or set autorun-never to true in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/media-handling]
autorun-never=true
Once the settings have been added, add a lock to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/media-handling/autorun-never
After the settings have been set, run dconf update.
Rationale
Automatically mounting file systems permits easy introduction of unknown devices, thereby facilitating malicious activity. Disabling automatic mount running in GNOME3 can prevent the introduction of malware via removable media. It will, however, also prevent desktop users from legitimate use of removable media.
Set GNOME3 Screensaver Inactivity Timeoutxccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_idle_delay mediumCCE-87170-7

Set GNOME3 Screensaver Inactivity Timeout

Rule IDxccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_idle_delay
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87170-7

References:
cis-csc1, 12, 15, 16
cjis5.5.5
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistAC-11(a), CM-6(a)
nist-csfPR.AC-7
pcidssReq-8.1.8
os-srgSRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
cis1.8.4
pcidss48.2.8, 8.2
Description
The idle time-out value for inactivity in the GNOME3 desktop is configured via the idle-delay setting must be set under an appropriate configuration file(s) in the /etc/dconf/db/local.d directory and locked in /etc/dconf/db/local.d/locks directory to prevent user modification.

For example, to configure the system for a 15 minute delay, add the following to /etc/dconf/db/local.d/00-security-settings:
[org/gnome/desktop/session]
idle-delay=uint32 900
Rationale
A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME3 can be configured to identify when a user's session has idled and take action to initiate a session lock.
Set GNOME3 Screensaver Lock Delay After Activation Periodxccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_delay mediumCCE-88417-1

Set GNOME3 Screensaver Lock Delay After Activation Period

Rule IDxccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_delay
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88417-1

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistAC-11(a), CM-6(a)
nist-csfPR.AC-7
pcidssReq-8.1.8
os-srgSRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
cis1.8.4
pcidss48.2.8, 8.2
Description
To activate the locking delay of the screensaver in the GNOME3 desktop when the screensaver is activated, add or set lock-delay to uint32 5 in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/screensaver]
lock-delay=uint32 5
         
After the settings have been set, run dconf update.
Rationale
A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not want to logout because of the temporary nature of the absense.
Ensure Users Cannot Change GNOME3 Screensaver Settingsxccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_user_locks mediumCCE-88349-6

Ensure Users Cannot Change GNOME3 Screensaver Settings

Rule IDxccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_user_locks
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88349-6

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a)
nist-csfPR.AC-7
os-srgSRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
cis1.8.5
Description
If not already configured, ensure that users cannot change GNOME3 screensaver lock settings by adding /org/gnome/desktop/screensaver/lock-delay to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/screensaver/lock-delay
After the settings have been set, run dconf update.
Rationale
A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the session lock. As such, users should not be allowed to change session settings.
Ensure Users Cannot Change GNOME3 Session Idle Settingsxccdf_org.ssgproject.content_rule_dconf_gnome_session_idle_user_locks mediumCCE-88587-1

Ensure Users Cannot Change GNOME3 Session Idle Settings

Rule IDxccdf_org.ssgproject.content_rule_dconf_gnome_session_idle_user_locks
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88587-1

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a)
nist-csfPR.AC-7
pcidssReq-8.1.8
os-srgSRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
cis1.8.5
pcidss48.2.8, 8.2
Description
If not already configured, ensure that users cannot change GNOME3 session idle settings by adding /org/gnome/desktop/session/idle-delay to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/session/idle-delay
After the settings have been set, run dconf update.
Rationale
A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the session lock. As such, users should not be allowed to change session settings.
Remove the GDM Package Groupxccdf_org.ssgproject.content_rule_package_gdm_removed mediumCCE-88880-0

Remove the GDM Package Group

Rule IDxccdf_org.ssgproject.content_rule_package_gdm_removed
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88880-0

References:
nistCM-7(a), CM-7(b), CM-6(a)
os-srgSRG-OS-000480-GPOS-00227
cis1.8.1, 2.1.20
Description
By removing the gdm package, the system no longer has GNOME installed installed. If X Windows is not installed then the system cannot boot into graphical user mode. This prevents the system from being accidentally or maliciously booted into a graphical.target mode. To do so, run the following command:
$ sudo yum remove gdm
Rationale
Unnecessary service packages must not be installed to decrease the attack surface of the system. A graphical environment is unnecessary for certain types of systems including a virtualization hypervisor.
Make sure that the dconf databases are up-to-date with regards to respective keyfilesxccdf_org.ssgproject.content_rule_dconf_db_up_to_date highCCE-86609-5

Make sure that the dconf databases are up-to-date with regards to respective keyfiles

Rule IDxccdf_org.ssgproject.content_rule_dconf_db_up_to_date
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severityhigh
Identifiers:

CCE-86609-5

References:
hipaa164.308(a)(1)(ii)(B), 164.308(a)(5)(ii)(A)
pcidssReq-6.2
os-srgSRG-OS-000480-GPOS-00227
cisreload_dconf_db
pcidss48.2.8, 8.2
Description
By default, DConf uses a binary database as a data backend. The system-level database is compiled from keyfiles in the /etc/dconf/db/ directory by the
dconf update
command. More specifically, content present in the following directories:
/etc/dconf/db/distro.d
/etc/dconf/db/local.d
Rationale
Unlike text-based keyfiles, the binary database is impossible to check by OVAL. Therefore, in order to evaluate dconf configuration, both have to be true at the same time - configuration files have to be compliant, and the database needs to be more recent than those keyfiles, which gives confidence that it reflects them.
Install sudo Packagexccdf_org.ssgproject.content_rule_package_sudo_installed mediumCCE-87100-4

Install sudo Package

Rule IDxccdf_org.ssgproject.content_rule_package_sudo_installed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_sudo_installed:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87100-4

References:
ism1382, 1384, 1386
nistCM-6(a)
osppFMT_MOF_EXT.1
os-srgSRG-OS-000324-GPOS-00125
anssiR33
cis5.2.1
pcidss42.2.6, 2.2
Description
The sudo package can be installed with the following command:
$ sudo dnf install sudo
Rationale
sudo is a program designed to allow a system administrator to give limited root privileges to users and log root activity. The basic philosophy is to give as few privileges as possible but still allow system users to get their work done.
OVAL test results details

package sudo is installed  oval:ssg-test_package_sudo_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedsudox86_64(none)8.p5.el101.9.150:1.9.15-8.p5.el10199e2f91fd431d51sudo-0:1.9.15-8.p5.el10.x86_64
Ensure Only Users Logged In To Real tty Can Execute Sudo - sudo use_ptyxccdf_org.ssgproject.content_rule_sudo_add_use_pty mediumCCE-89073-1

Ensure Only Users Logged In To Real tty Can Execute Sudo - sudo use_pty

Rule IDxccdf_org.ssgproject.content_rule_sudo_add_use_pty
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sudo_add_use_pty:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89073-1

References:
pcidssReq-10.2.5
anssiR39
cis5.2.2
pcidss42.2.6, 2.2
Description
The sudo use_pty tag, when specified, will only execute sudo commands from users logged in to a real tty. This should be enabled by making sure that the use_pty tag exists in /etc/sudoers configuration file or any sudo configuration snippets in /etc/sudoers.d/.
Rationale
Requiring that sudo commands be run in a pseudo-terminal can prevent an attacker from retaining access to the user's terminal after the main program has finished executing.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q sudo; then

if /usr/sbin/visudo -qcf /etc/sudoers; then
    cp /etc/sudoers /etc/sudoers.bak
    if ! grep -P '^[\s]*Defaults\b[^!\n]*\buse_pty.*$' /etc/sudoers; then
        # sudoers file doesn't define Option use_pty
        echo "Defaults use_pty" >> /etc/sudoers
    fi
    
    # Check validity of sudoers and cleanup bak
    if /usr/sbin/visudo -qcf /etc/sudoers; then
        rm -f /etc/sudoers.bak
    else
        echo "Fail to validate remediated /etc/sudoers, reverting to original file."
        mv /etc/sudoers.bak /etc/sudoers
        false
    fi
else
    echo "Skipping remediation, /etc/sudoers failed to validate"
    false
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89073-1
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_add_use_pty

- name: Ensure use_pty is enabled in /etc/sudoers
  lineinfile:
    path: /etc/sudoers
    regexp: ^[\s]*Defaults.*\buse_pty\b.*$
    line: Defaults use_pty
    validate: /usr/sbin/visudo -cf %s
  when: '"sudo" in ansible_facts.packages'
  tags:
  - CCE-89073-1
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_add_use_pty
OVAL test results details

use_pty exists in /etc/sudoers or /etc/sudoers.d/  oval:ssg-test_use_pty_sudoers:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_use_pty_sudoers:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/sudoers(|\.d/.*)$^[\s]*Defaults\b[^!\n]*\buse_pty.*$1
Ensure Sudo Logfile Exists - sudo logfilexccdf_org.ssgproject.content_rule_sudo_custom_logfile lowCCE-89611-8

Ensure Sudo Logfile Exists - sudo logfile

Rule IDxccdf_org.ssgproject.content_rule_sudo_custom_logfile
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sudo_custom_logfile:def:1
Time2025-07-22T08:31:38+00:00
Severitylow
Identifiers:

CCE-89611-8

References:
pcidssReq-10.2.5
cis5.2.3
pcidss42.2.6, 2.2
Description
A custom log sudo file can be configured with the 'logfile' tag. This rule configures a sudo custom logfile at the default location suggested by CIS, which uses /var/log/sudo.log.
Rationale
A sudo log file simplifies auditing of sudo commands.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q sudo; then

var_sudo_logfile='/var/log/sudo.log'


if /usr/sbin/visudo -qcf /etc/sudoers; then
    cp /etc/sudoers /etc/sudoers.bak
    if ! grep -P '^[\s]*Defaults\b[^!\n]*\blogfile\s*=\s*(?:"?([^",\s]+)"?).*$' /etc/sudoers; then
        # sudoers file doesn't define Option logfile
        echo "Defaults logfile=${var_sudo_logfile}" >> /etc/sudoers
    else
        # sudoers file defines Option logfile, remediate if appropriate value is not set
        if ! grep -P "^[\s]*Defaults.*\blogfile=${var_sudo_logfile}\b.*$" /etc/sudoers; then
            
            escaped_variable=${var_sudo_logfile//$'/'/$'\/'}
            sed -Ei "s/(^[\s]*Defaults.*\blogfile=)[-]?.+(\b.*$)/\1$escaped_variable\2/" /etc/sudoers
        fi
    fi
    
    # Check validity of sudoers and cleanup bak
    if /usr/sbin/visudo -qcf /etc/sudoers; then
        rm -f /etc/sudoers.bak
    else
        echo "Fail to validate remediated /etc/sudoers, reverting to original file."
        mv /etc/sudoers.bak /etc/sudoers
        false
    fi
else
    echo "Skipping remediation, /etc/sudoers failed to validate"
    false
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89611-8
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_custom_logfile
- name: XCCDF Value var_sudo_logfile # promote to variable
  set_fact:
    var_sudo_logfile: !!str /var/log/sudo.log
  tags:
    - always

- name: Ensure logfile is enabled with the appropriate value in /etc/sudoers
  lineinfile:
    path: /etc/sudoers
    regexp: ^[\s]*Defaults\s(.*)\blogfile=[-]?.+\b(.*)$
    line: Defaults \1logfile={{ var_sudo_logfile }}\2
    validate: /usr/sbin/visudo -cf %s
    backrefs: true
  register: edit_sudoers_logfile_option
  when: '"sudo" in ansible_facts.packages'
  tags:
  - CCE-89611-8
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_custom_logfile

- name: Enable logfile option with appropriate value in /etc/sudoers
  lineinfile:
    path: /etc/sudoers
    line: Defaults logfile={{ var_sudo_logfile }}
    validate: /usr/sbin/visudo -cf %s
  when:
  - '"sudo" in ansible_facts.packages'
  - edit_sudoers_logfile_option is defined and not edit_sudoers_logfile_option.changed
  tags:
  - CCE-89611-8
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_custom_logfile
OVAL test results details

logfile exists in /etc/sudoers or /etc/sudoers.d/  oval:ssg-test_logfile_sudoers:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_logfile_sudoers:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/sudoers(|\.d/.*)$^[\s]*Defaults\b[^!\n]*\blogfile\s*=\s*(?:"?([^",\s]+)"?).*$1
Ensure Users Re-Authenticate for Privilege Escalation - sudoxccdf_org.ssgproject.content_rule_sudo_require_authentication mediumCCE-87457-8

Ensure Users Re-Authenticate for Privilege Escalation - sudo

Rule IDxccdf_org.ssgproject.content_rule_sudo_require_authentication
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sudo_require_authentication:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87457-8

References:
cis-csc1, 12, 15, 16, 5
cobit5DSS05.04, DSS05.10, DSS06.03, DSS06.10
isa-62443-20094.3.3.5.1, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-11, CM-6(a)
nist-csfPR.AC-1, PR.AC-7
os-srgSRG-OS-000373-GPOS-00156
cis5.2.4
pcidss42.2.6, 2.2
Description
The sudo NOPASSWD and !authenticate option, when specified, allows a user to execute commands using sudo without having to authenticate. This should be disabled by making sure that NOPASSWD and/or !authenticate do not exist in /etc/sudoers configuration file or any sudo configuration snippets in /etc/sudoers.d/."
Rationale
Without re-authentication, users may access resources or perform tasks for which they do not have authorization.

When operating systems provide the capability to escalate a functional capability, it is critical that the user re-authenticate.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict

for f in /etc/sudoers /etc/sudoers.d/* ; do
  if [ ! -e "$f" ] ; then
    continue
  fi
  matching_list=$(grep -P '^(?!#).*[\s]+NOPASSWD[\s]*\:.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      # comment out "NOPASSWD" matches to preserve user data
      sed -i "s|^${entry}$|# &|g" $f
    done <<< "$matching_list"

    /usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
  fi
done

for f in /etc/sudoers /etc/sudoers.d/* ; do
  if [ ! -e "$f" ] ; then
    continue
  fi
  matching_list=$(grep -P '^(?!#).*[\s]+\!authenticate.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      # comment out "!authenticate" matches to preserve user data
      sed -i "s|^${entry}$|# &|g" $f
    done <<< "$matching_list"

    /usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
  fi
done

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Find /etc/sudoers.d/ files
  ansible.builtin.find:
    paths:
    - /etc/sudoers.d/
  register: sudoers
  tags:
  - CCE-87457-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_authentication

- name: Remove lines containing NOPASSWD from sudoers files
  ansible.builtin.replace:
    regexp: (^(?!#).*[\s]+NOPASSWD[\s]*\:.*$)
    replace: '# \g<1>'
    path: '{{ item.path }}'
    validate: /usr/sbin/visudo -cf %s
  with_items:
  - path: /etc/sudoers
  - '{{ sudoers.files }}'
  tags:
  - CCE-87457-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_authentication

- name: Find /etc/sudoers.d/ files
  ansible.builtin.find:
    paths:
    - /etc/sudoers.d/
  register: sudoers
  tags:
  - CCE-87457-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_authentication

- name: Remove lines containing !authenticate from sudoers files
  ansible.builtin.replace:
    regexp: (^(?!#).*[\s]+\!authenticate.*$)
    replace: '# \g<1>'
    path: '{{ item.path }}'
    validate: /usr/sbin/visudo -cf %s
  with_items:
  - path: /etc/sudoers
  - '{{ sudoers.files }}'
  tags:
  - CCE-87457-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_authentication
OVAL test results details

!authenticate does not exist in /etc/sudoers  oval:ssg-test_no_authenticate_etc_sudoers:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_no_authenticate_etc_sudoers:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/sudoers^(?!#).*[\s]+\!authenticate.*$1

!authenticate does not exist in /etc/sudoers.d  oval:ssg-test_no_authenticate_etc_sudoers_d:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_no_authenticate_etc_sudoers_d:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/sudoers.d^.*$^(?!#).*[\s]+\!authenticate.*$1

NOPASSWD does not exist /etc/sudoers  oval:ssg-test_nopasswd_etc_sudoers:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_nopasswd_etc_sudoers:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/sudoers^(?!#).*[\s]+NOPASSWD[\s]*\:.*$1

NOPASSWD does not exist in /etc/sudoers.d  oval:ssg-test_nopasswd_etc_sudoers_d:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/sudoers.d/90-cloud-init-usersec2-user ALL=(ALL) NOPASSWD:ALL
Require Re-Authentication When Using the sudo Commandxccdf_org.ssgproject.content_rule_sudo_require_reauthentication mediumCCE-88136-7

Require Re-Authentication When Using the sudo Command

Rule IDxccdf_org.ssgproject.content_rule_sudo_require_reauthentication
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sudo_require_reauthentication:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88136-7

References:
nistIA-11
os-srgSRG-OS-000373-GPOS-00156, SRG-OS-000373-GPOS-00157, SRG-OS-000373-GPOS-00158
cis5.2.5, 5.2.6
pcidss42.2.6, 2.2
Description
The sudo timestamp_timeout tag sets the amount of time sudo password prompt waits. The default timestamp_timeout value is 5 minutes. The timestamp_timeout should be configured by making sure that the timestamp_timeout tag exists in /etc/sudoers configuration file or any sudo configuration snippets in /etc/sudoers.d/. If the value is set to an integer less than 0, the user's time stamp will not expire and the user will not have to re-authenticate for privileged actions until the user's session is terminated.
Rationale
Without re-authentication, users may access resources or perform tasks for which they do not have authorization.

When operating systems provide the capability to escalate a functional capability, it is critical that the user re-authenticate.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q sudo; then

var_sudo_timestamp_timeout='5'


if grep -Px '^[\s]*Defaults.*timestamp_timeout[\s]*=.*' /etc/sudoers.d/*; then
    find /etc/sudoers.d/ -type f -exec sed -Ei "/^[[:blank:]]*Defaults.*timestamp_timeout[[:blank:]]*=.*/d" {} \;
fi

if /usr/sbin/visudo -qcf /etc/sudoers; then
    cp /etc/sudoers /etc/sudoers.bak
    if ! grep -P '^[\s]*Defaults.*timestamp_timeout[\s]*=[\s]*[-]?\w+.*$' /etc/sudoers; then
        # sudoers file doesn't define Option timestamp_timeout
        echo "Defaults timestamp_timeout=${var_sudo_timestamp_timeout}" >> /etc/sudoers
    else
        # sudoers file defines Option timestamp_timeout, remediate wrong values if present
        if grep -qP "^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=[\s]*(?!${var_sudo_timestamp_timeout}\b)[-]?\w+\b.*$" /etc/sudoers; then
            sed -Ei "s/(^[[:blank:]]*Defaults.*timestamp_timeout[[:blank:]]*=)[[:blank:]]*[-]?\w+(.*$)/\1${var_sudo_timestamp_timeout}\2/" /etc/sudoers
        fi
    fi
    
    # Check validity of sudoers and cleanup bak
    if /usr/sbin/visudo -qcf /etc/sudoers; then
        rm -f /etc/sudoers.bak
    else
        echo "Fail to validate remediated /etc/sudoers, reverting to original file."
        mv /etc/sudoers.bak /etc/sudoers
        false
    fi
else
    echo "Skipping remediation, /etc/sudoers failed to validate"
    false
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88136-7
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication
- name: XCCDF Value var_sudo_timestamp_timeout # promote to variable
  set_fact:
    var_sudo_timestamp_timeout: !!str 5
  tags:
    - always

- name: Require Re-Authentication When Using the sudo Command - Find /etc/sudoers.d/*
    files containing 'Defaults timestamp_timeout'
  ansible.builtin.find:
    path: /etc/sudoers.d
    patterns: '*'
    contains: ^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=.*
  register: sudoers_d_defaults_timestamp_timeout
  when: '"sudo" in ansible_facts.packages'
  tags:
  - CCE-88136-7
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication

- name: Require Re-Authentication When Using the sudo Command - Remove 'Defaults timestamp_timeout'
    from /etc/sudoers.d/* files
  ansible.builtin.lineinfile:
    path: '{{ item.path }}'
    regexp: ^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=.*
    state: absent
  with_items: '{{ sudoers_d_defaults_timestamp_timeout.files }}'
  when: '"sudo" in ansible_facts.packages'
  tags:
  - CCE-88136-7
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication

- name: Require Re-Authentication When Using the sudo Command - Ensure timestamp_timeout
    has the appropriate value in /etc/sudoers
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    regexp: ^[\s]*Defaults\s(.*)\btimestamp_timeout[\s]*=[\s]*[-]?\w+\b(.*)$
    line: Defaults \1timestamp_timeout={{ var_sudo_timestamp_timeout }}\2
    validate: /usr/sbin/visudo -cf %s
    backrefs: true
  register: edit_sudoers_timestamp_timeout_option
  when: '"sudo" in ansible_facts.packages'
  tags:
  - CCE-88136-7
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication

- name: Require Re-Authentication When Using the sudo Command - Enable timestamp_timeout
    option with correct value in /etc/sudoers
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    line: Defaults timestamp_timeout={{ var_sudo_timestamp_timeout }}
    validate: /usr/sbin/visudo -cf %s
  when:
  - '"sudo" in ansible_facts.packages'
  - |
    edit_sudoers_timestamp_timeout_option is defined and not edit_sudoers_timestamp_timeout_option.changed
  tags:
  - CCE-88136-7
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication

- name: Require Re-Authentication When Using the sudo Command - Remove timestamp_timeout
    wrong values in /etc/sudoers
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    regexp: ^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=[\s]*(?!{{ var_sudo_timestamp_timeout
      }}\b)[-]?\w+\b.*$
    state: absent
    validate: /usr/sbin/visudo -cf %s
  when: '"sudo" in ansible_facts.packages'
  tags:
  - CCE-88136-7
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication
OVAL test results details

check correct configuration in /etc/sudoers  oval:ssg-test_sudo_timestamp_timeout:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_sudo_timestamp_timeout:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\/etc\/(sudoers|sudoers\.d\/.*)$^[\s]*Defaults[\s]+timestamp_timeout[\s]*=\s*[+]?(\d*\.\d+|\d+\.\d*|\d+)$1

check correct configuration in /etc/sudoers  oval:ssg-test_sudo_timestamp_timeout_no_signs:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sudo_timestamp_timeout_no_signs:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\/etc\/(sudoers|sudoers\.d\/.*)$^[\s]*Defaults[\s]+timestamp_timeout[\s]*=\s*[\-](\d*\.\d+|\d+\.\d*|\d+)$1
Ensure gpgcheck Enabled In Main dnf Configurationxccdf_org.ssgproject.content_rule_ensure_gpgcheck_globally_activated highCCE-88404-9

Ensure gpgcheck Enabled In Main dnf Configuration

Rule IDxccdf_org.ssgproject.content_rule_ensure_gpgcheck_globally_activated
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-ensure_gpgcheck_globally_activated:def:1
Time2025-07-22T08:31:38+00:00
Severityhigh
Identifiers:

CCE-88404-9

References:
cis-csc11, 2, 3, 9
cjis5.10.4.1
cobit5APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui3.4.8
hipaa164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), SA-12, SA-12(10), CM-11(a), CM-11(b)
nist-csfPR.DS-6, PR.DS-8, PR.IP-1
osppFPT_TUD_EXT.1, FPT_TUD_EXT.2
pcidssReq-6.2
os-srgSRG-OS-000366-GPOS-00153
anssiR59
cis1.2.1.2
pcidss46.3.3, 6.3
Description
The gpgcheck option controls whether RPM packages' signatures are always checked prior to installation. To configure dnf to check package signatures before installing them, ensure the following line appears in /etc/dnf/dnf.conf in the [main] section:
gpgcheck=1
Rationale
Changes to any software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor.
Accordingly, patches, service packs, device drivers, or operating system components must be signed with a certificate recognized and approved by the organization.
Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA).
OVAL test results details

check value of gpgcheck in /etc/dnf/dnf.conf  oval:ssg-test_ensure_gpgcheck_globally_activated:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/dnf/dnf.confgpgcheck=1
Enable GNOME3 Login Warning Bannerxccdf_org.ssgproject.content_rule_dconf_gnome_banner_enabled mediumCCE-87417-2

Enable GNOME3 Login Warning Banner

Rule IDxccdf_org.ssgproject.content_rule_dconf_gnome_banner_enabled
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87417-2

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.9
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistAC-8(a), AC-8(b), AC-8(c)
nist-csfPR.AC-7
os-srgSRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088
cis1.8.2
Description
In the default graphical environment, displaying a login warning banner in the GNOME Display Manager's login screen can be enabled on the login screen by setting banner-message-enable to true.

To enable, add or edit banner-message-enable to /etc/dconf/db/distro.d/00-security-settings. For example:
[org/gnome/login-screen]
banner-message-enable=true
Once the setting has been added, add a lock to /etc/dconf/db/distro.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/login-screen/banner-message-enable
After the settings have been set, run dconf update. The banner text must also be set.
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.

For U.S. Government systems, system use notifications are required only for access via login interfaces with human users and are not required when such human interfaces do not exist.
Ensure Local Login Warning Banner Is Configured Properlyxccdf_org.ssgproject.content_rule_banner_etc_issue_cis mediumCCE-86165-8

Ensure Local Login Warning Banner Is Configured Properly

Rule IDxccdf_org.ssgproject.content_rule_banner_etc_issue_cis
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-banner_etc_issue_cis:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86165-8

References:
cis1.7.2
Description
To configure the system local login warning banner edit the /etc/issue file. The contents of this file is displayed to users prior to login to local terminals. Replace the default text with a message compliant with the local site policy. The message should not contain information about operating system version, release, kernel version or patch level. The recommended banner text can be tailored in the XCCDF Value xccdf_org.ssgproject.content_value_cis_banner_text:
Authorized users only. All activity may be monitored and reported.
Rationale
Warning messages inform users who are attempting to login to the system of their legal status regarding the system and must include the name of the organization that owns the system and any monitoring policies that are in place. Displaying OS and patch level information in login banners also has the side effect of providing detailed system information to attackers attempting to target specific exploits of a system. Authorized users can easily get this information by running the uname -a command once they have logged in.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

cis_banner_text='Authorized users only. All activity may be monitored and reported.'

echo "$cis_banner_text" > "/etc/issue"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86165-8
  - banner_etc_issue_cis
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value cis_banner_text # promote to variable
  set_fact:
    cis_banner_text: !!str Authorized users only. All activity may be monitored and reported.
  tags:
    - always

- name: Ensure Local Login Warning Banner Is Configured Properly - Copy using inline
    content
  ansible.builtin.copy:
    content: '{{ cis_banner_text }}'
    dest: /etc/issue
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86165-8
  - banner_etc_issue_cis
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

Check that the given object does not exist  oval:ssg-test_banner_etc_issue_cis_file_nonempty:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/issue\S
not evaluated/etc/issueKernel \r on \m

Check that the given object does not exist  oval:ssg-test_banner_etc_issue_cis:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/issue\m
not evaluated/etc/issue\r
Ensure Remote Login Warning Banner Is Configured Properlyxccdf_org.ssgproject.content_rule_banner_etc_issue_net_cis mediumCCE-86169-0

Ensure Remote Login Warning Banner Is Configured Properly

Rule IDxccdf_org.ssgproject.content_rule_banner_etc_issue_net_cis
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-banner_etc_issue_net_cis:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86169-0

References:
cis1.7.3
Description
To configure the system remote login warning banner edit the /etc/issue.net file. The contents of this file is displayed to users prior to login from remote connections. Replace the default text with a message compliant with the local site policy. The message should not contain information about operating system version, release, kernel version or patch level. The recommended banner text can be tailored in the XCCDF Value xccdf_org.ssgproject.content_value_cis_banner_text:
Authorized users only. All activity may be monitored and reported.
Rationale
Warning messages inform users who are attempting to login to the system of their legal status regarding the system and must include the name of the organization that owns the system and any monitoring policies that are in place. Displaying OS and patch level information in login banners also has the side effect of providing detailed system information to attackers attempting to target specific exploits of a system. Authorized users can easily get this information by running the uname -a command once they have logged in.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

cis_banner_text='Authorized users only. All activity may be monitored and reported.'

echo "$cis_banner_text" > "/etc/issue.net"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86169-0
  - banner_etc_issue_net_cis
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value cis_banner_text # promote to variable
  set_fact:
    cis_banner_text: !!str Authorized users only. All activity may be monitored and reported.
  tags:
    - always

- name: Ensure Remote Login Warning Banner Is Configured Properly - Copy using inline
    content
  ansible.builtin.copy:
    content: '{{ cis_banner_text }}'
    dest: /etc/issue.net
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86169-0
  - banner_etc_issue_net_cis
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

Check that the given object does not exist  oval:ssg-test_banner_etc_issue_net_cis_file_nonempty:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/issue.net\S
not evaluated/etc/issue.netKernel \r on \m

Check that the given object does not exist  oval:ssg-test_banner_etc_issue_net_cis:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/issue.net\r
not evaluated/etc/issue.net\m
Ensure Message Of The Day Is Configured Properlyxccdf_org.ssgproject.content_rule_banner_etc_motd_cis mediumCCE-86150-0

Ensure Message Of The Day Is Configured Properly

Rule IDxccdf_org.ssgproject.content_rule_banner_etc_motd_cis
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-banner_etc_motd_cis:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86150-0

References:
cis1.7.1
Description
To configure the system message of the day banner edit the /etc/motd file. Replace the default text with a message compliant with the local site policy. The message should not contain information about operating system version, release, kernel version or patch level. The recommended banner text can be tailored in the XCCDF Value xccdf_org.ssgproject.content_value_cis_banner_text:
Authorized users only. All activity may be monitored and reported.
Rationale
Warning messages inform users who are attempting to login to the system of their legal status regarding the system and must include the name of the organization that owns the system and any monitoring policies that are in place. Displaying OS and patch level information in login banners also has the side effect of providing detailed system information to attackers attempting to target specific exploits of a system. Authorized users can easily get this information by running the uname -a command once they have logged in.
OVAL test results details

Check that the given object does not exist  oval:ssg-test_banner_etc_motd_cis:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_banner_etc_motd_cis:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/motd(\\v|\\r|\\m|\\s|rhel10)1
Verify Group Ownership of System Login Bannerxccdf_org.ssgproject.content_rule_file_groupowner_etc_issue mediumCCE-89209-1

Verify Group Ownership of System Login Banner

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_etc_issue
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_etc_issue:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89209-1

References:
cis1.7.5
Description
To properly set the group owner of /etc/issue, run the command:
$ sudo chgrp root /etc/issue
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper group ownership will ensure that only root user can modify the banner.
OVAL test results details

Testing group ownership of /etc/issue  oval:ssg-test_file_groupowner_etc_issue_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_etc_issue_0:obj:1 of type file_object
FilepathFilterFilter
/etc/issueoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_etc_issue_0_0:ste:1
Verify Group Ownership of System Login Banner for Remote Connectionsxccdf_org.ssgproject.content_rule_file_groupowner_etc_issue_net mediumCCE-88343-9

Verify Group Ownership of System Login Banner for Remote Connections

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_etc_issue_net
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_etc_issue_net:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88343-9

References:
cis1.7.6
pcidss41.2.8, 1.2
Description
To properly set the group owner of /etc/issue.net, run the command:
$ sudo chgrp root /etc/issue.net
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper group ownership will ensure that only root user can modify the banner.
OVAL test results details

Testing group ownership of /etc/issue.net  oval:ssg-test_file_groupowner_etc_issue_net_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_etc_issue_net_0:obj:1 of type file_object
FilepathFilterFilter
/etc/issue.netoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_etc_issue_net_0_0:ste:1
Verify Group Ownership of Message of the Day Bannerxccdf_org.ssgproject.content_rule_file_groupowner_etc_motd mediumCCE-87687-0

Verify Group Ownership of Message of the Day Banner

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_etc_motd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_etc_motd:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87687-0

References:
cis1.7.4
Description
To properly set the group owner of /etc/motd, run the command:
$ sudo chgrp root /etc/motd
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper group ownership will ensure that only root user can modify the banner.
OVAL test results details

Testing group ownership of /etc/motd  oval:ssg-test_file_groupowner_etc_motd_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_etc_motd_0:obj:1 of type file_object
FilepathFilterFilter
/etc/motdoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_etc_motd_0_0:ste:1
Verify ownership of System Login Bannerxccdf_org.ssgproject.content_rule_file_owner_etc_issue mediumCCE-88544-2

Verify ownership of System Login Banner

Rule IDxccdf_org.ssgproject.content_rule_file_owner_etc_issue
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_etc_issue:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88544-2

References:
cis1.7.5
Description
To properly set the owner of /etc/issue, run the command:
$ sudo chown root /etc/issue 
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper ownership will ensure that only root user can modify the banner.
OVAL test results details

Testing user ownership of /etc/issue  oval:ssg-test_file_owner_etc_issue_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_etc_issue_0:obj:1 of type file_object
FilepathFilterFilter
/etc/issueoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_etc_issue_0_0:ste:1
Verify ownership of System Login Banner for Remote Connectionsxccdf_org.ssgproject.content_rule_file_owner_etc_issue_net mediumCCE-86969-3

Verify ownership of System Login Banner for Remote Connections

Rule IDxccdf_org.ssgproject.content_rule_file_owner_etc_issue_net
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_etc_issue_net:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86969-3

References:
cis1.7.6
pcidss41.2.8, 1.2
Description
To properly set the owner of /etc/issue.net, run the command:
$ sudo chown root /etc/issue.net 
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper ownership will ensure that only root user can modify the banner.
OVAL test results details

Testing user ownership of /etc/issue.net  oval:ssg-test_file_owner_etc_issue_net_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_etc_issue_net_0:obj:1 of type file_object
FilepathFilterFilter
/etc/issue.netoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_etc_issue_net_0_0:ste:1
Verify ownership of Message of the Day Bannerxccdf_org.ssgproject.content_rule_file_owner_etc_motd mediumCCE-87043-6

Verify ownership of Message of the Day Banner

Rule IDxccdf_org.ssgproject.content_rule_file_owner_etc_motd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_etc_motd:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87043-6

References:
cis1.7.4
Description
To properly set the owner of /etc/motd, run the command:
$ sudo chown root /etc/motd 
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper ownership will ensure that only root user can modify the banner.
OVAL test results details

Testing user ownership of /etc/motd  oval:ssg-test_file_owner_etc_motd_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_etc_motd_0:obj:1 of type file_object
FilepathFilterFilter
/etc/motdoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_etc_motd_0_0:ste:1
Verify permissions on System Login Bannerxccdf_org.ssgproject.content_rule_file_permissions_etc_issue mediumCCE-86812-5

Verify permissions on System Login Banner

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_etc_issue
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_etc_issue:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86812-5

References:
cis1.7.5
Description
To properly set the permissions of /etc/issue, run the command:
$ sudo chmod 0644 /etc/issue
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper permissions will ensure that only root user can modify the banner.
OVAL test results details

Testing mode of /etc/issue  oval:ssg-test_file_permissions_etc_issue_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_etc_issue_0:obj:1 of type file_object
FilepathFilterFilter
/etc/issueoval:ssg-exclude_symlinks__etc_issue:ste:1oval:ssg-state_file_permissions_etc_issue_0_mode_0644or_stricter_:ste:1
Verify permissions on System Login Banner for Remote Connectionsxccdf_org.ssgproject.content_rule_file_permissions_etc_issue_net mediumCCE-87831-4

Verify permissions on System Login Banner for Remote Connections

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_etc_issue_net
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_etc_issue_net:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87831-4

References:
cis1.7.6
pcidss41.2.8, 1.2
Description
To properly set the permissions of /etc/issue.net, run the command:
$ sudo chmod 0644 /etc/issue.net
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper permissions will ensure that only root user can modify the banner.
OVAL test results details

Testing mode of /etc/issue.net  oval:ssg-test_file_permissions_etc_issue_net_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_etc_issue_net_0:obj:1 of type file_object
FilepathFilterFilter
/etc/issue.netoval:ssg-exclude_symlinks__etc_issue_net:ste:1oval:ssg-state_file_permissions_etc_issue_net_0_mode_0644or_stricter_:ste:1
Verify permissions on Message of the Day Bannerxccdf_org.ssgproject.content_rule_file_permissions_etc_motd mediumCCE-90411-0

Verify permissions on Message of the Day Banner

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_etc_motd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_etc_motd:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-90411-0

References:
cis1.7.4
Description
To properly set the permissions of /etc/motd, run the command:
$ sudo chmod 0644 /etc/motd
Rationale
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper permissions will ensure that only root user can modify the banner.
OVAL test results details

Testing mode of /etc/motd  oval:ssg-test_file_permissions_etc_motd_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_etc_motd_0:obj:1 of type file_object
FilepathFilterFilter
/etc/motdoval:ssg-exclude_symlinks__etc_motd:ste:1oval:ssg-state_file_permissions_etc_motd_0_mode_0644or_stricter_:ste:1
Limit Password Reuse: password-authxccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth mediumCCE-89749-6

Limit Password Reuse: password-auth

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_pam_pwhistory_remember_password_auth:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89749-6

References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.1.1
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.8
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(f), IA-5(1)(e)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.5
os-srgSRG-OS-000077-GPOS-00045
cis5.3.3.3.1
pcidss48.3.7, 8.3
Description
Do not allow users to reuse recent passwords. This can be accomplished by using the remember option for the pam_pwhistory PAM module.

On systems with newer versions of authselect, the pam_pwhistory PAM module can be enabled via authselect feature:
authselect enable-feature with-pwhistory
Otherwise, it should be enabled using an authselect custom profile.

Newer systems also have the /etc/security/pwhistory.conf file for setting pam_pwhistory module options. This file should be used whenever available. Otherwise, the pam_pwhistory module options can be set in PAM files.

The value for remember option must be equal or greater than 24
Rationale
Preventing re-use of previous passwords helps ensure that a compromised password is not re-used by a user.
Warnings
warning  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report.
warning  Newer versions of authselect contain an authselect feature to easily and properly enable pam_pwhistory.so module. If this feature is not yet available in your system, an authselect custom profile must be used to avoid integrity issues in PAM files. If a custom profile was created and used in the system before this authselect feature was available, the new feature can't be used with this custom profile and the remediation will fail. In this case, the custom profile should be recreated or manually updated.

# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

var_password_pam_remember='24'
var_password_pam_remember_control_flag='requisite,required'


var_password_pam_remember_control_flag="$(echo $var_password_pam_remember_control_flag | cut -d \, -f 1)"

if [ -f /usr/bin/authselect ]; then
    if authselect list-features sssd | grep -q with-pwhistory; then
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi
        authselect enable-feature with-pwhistory

        authselect apply-changes -b
    else
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            # The "local" profile does not contain essential security features required by multiple Benchmarks.
            # If currently used, it is replaced by "sssd", which is the best option in this case.
            if [[ $CURRENT_PROFILE == local ]]; then
                CURRENT_PROFILE="sssd"
            fi
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
        
        if ! grep -qP "^\s*password\s+\$var_password_pam_remember_control_flag\s+pam_pwhistory.so\s*.*" "$PAM_FILE_PATH"; then
            # Line matching group + control + module was not found. Check group + module.
            if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
                # The control is updated only if one single line matches.
                sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1$var_password_pam_remember_control_flag \2/" "$PAM_FILE_PATH"
            else
                LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
                if [ ! -z $LAST_MATCH_LINE ]; then
                    sed -i --follow-symlinks $LAST_MATCH_LINE" a password     $var_password_pam_remember_control_flag    pam_pwhistory.so" "$PAM_FILE_PATH"
                else
                    echo "password    $var_password_pam_remember_control_flag    pam_pwhistory.so" >> "$PAM_FILE_PATH"
                fi
            fi
        fi
    fi
else

    
    if ! grep -qP "^\s*password\s+\$var_password_pam_remember_control_flag\s+pam_pwhistory.so\s*.*" "/etc/pam.d/password-auth"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "/etc/pam.d/password-auth")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1$var_password_pam_remember_control_flag \2/" "/etc/pam.d/password-auth"
        else
            LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "/etc/pam.d/password-auth" | tail -n 1 | cut -d: -f 1)
            if [ ! -z $LAST_MATCH_LINE ]; then
                sed -i --follow-symlinks $LAST_MATCH_LINE" a password     $var_password_pam_remember_control_flag    pam_pwhistory.so" "/etc/pam.d/password-auth"
            else
                echo "password    $var_password_pam_remember_control_flag    pam_pwhistory.so" >> "/etc/pam.d/password-auth"
            fi
        fi
    fi

fi

PWHISTORY_CONF="/etc/security/pwhistory.conf"
if [ -f $PWHISTORY_CONF ]; then
    regex="^\s*remember\s*="
    line="remember = $var_password_pam_remember"
    if ! grep -q $regex $PWHISTORY_CONF; then
        echo $line >> $PWHISTORY_CONF
    else
        sed -i --follow-symlinks 's|^\s*\(remember\s*=\s*\)\(\S\+\)|\1'"$var_password_pam_remember"'|g' $PWHISTORY_CONF
    fi
    if [ -e "/etc/pam.d/password-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/password-auth"
        if [ -f /usr/bin/authselect ]; then
            
            if ! authselect check; then
            echo "
            authselect integrity check failed. Remediation aborted!
            This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
            It is not recommended to manually edit the PAM files when authselect tool is available.
            In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
            exit 1
            fi

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"
                
                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done
                
                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi
        
    if grep -qP "^\s*password\s.*\bpam_pwhistory.so\s.*\bremember\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwhistory.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then
            
            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/password-auth was not found" >&2
    fi
else
    PAM_FILE_PATH="/etc/pam.d/password-auth"
    if [ -f /usr/bin/authselect ]; then
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            # The "local" profile does not contain essential security features required by multiple Benchmarks.
            # If currently used, it is replaced by "sssd", which is the best option in this case.
            if [[ $CURRENT_PROFILE == local ]]; then
                CURRENT_PROFILE="sssd"
            fi
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
    fi
    

    if ! grep -qP "^\s*password\s+requisite\s+pam_pwhistory.so\s*.*" "$PAM_FILE_PATH"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1requisite \2/" "$PAM_FILE_PATH"
        else
            echo "password    requisite    pam_pwhistory.so" >> "$PAM_FILE_PATH"
        fi
    fi
    # Check the option
    if ! grep -qP "^\s*password\s+requisite\s+pam_pwhistory.so\s*.*\sremember\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "/\s*password\s+requisite\s+pam_pwhistory.so.*/ s/$/ remember=$var_password_pam_remember/" "$PAM_FILE_PATH"
    else
        sed -i -E --follow-symlinks "s/(\s*password\s+requisite\s+pam_pwhistory.so\s+.*)(remember=)[[:alnum:]]*\s*(.*)/\1\2$var_password_pam_remember \3/" "$PAM_FILE_PATH"
    fi
    if [ -f /usr/bin/authselect ]; then
        
        authselect apply-changes -b
    fi
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89749-6
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
- name: XCCDF Value var_password_pam_remember # promote to variable
  set_fact:
    var_password_pam_remember: !!str 24
  tags:
    - always
- name: XCCDF Value var_password_pam_remember_control_flag # promote to variable
  set_fact:
    var_password_pam_remember_control_flag: !!str requisite,required
  tags:
    - always

- name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
    tool'
  ansible.builtin.stat:
    path: /usr/bin/authselect
  register: result_authselect_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-89749-6
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - Collect the available authselect features'
  ansible.builtin.command:
    cmd: authselect list-features sssd
  register: result_authselect_available_features
  changed_when: false
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  tags:
  - CCE-89749-6
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - Enable pam_pwhistory.so using authselect
    feature'
  block:

  - name: 'Limit Password Reuse: password-auth - Check integrity of authselect current
      profile'
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: 'Limit Password Reuse: password-auth - Informative message based on the
      authselect integrity check result'
    ansible.builtin.assert:
      that:
      - result_authselect_check_cmd.rc == 0
      fail_msg:
      - authselect integrity check failed. Remediation aborted!
      - This remediation could not be applied because an authselect profile was not
        selected or the selected profile is not intact.
      - It is not recommended to manually edit the PAM files when authselect tool
        is available.
      - In cases where the default authselect profile does not cover a specific demand,
        a custom authselect profile is recommended.
      success_msg:
      - authselect integrity check passed

  - name: 'Limit Password Reuse: password-auth - Get authselect current features'
    ansible.builtin.shell:
      cmd: authselect current | tail -n+3 | awk '{ print $2 }'
    register: result_authselect_features
    changed_when: false
    when:
    - result_authselect_check_cmd is success

  - name: 'Limit Password Reuse: password-auth - Ensure "with-pwhistory" feature is
      enabled using authselect tool'
    ansible.builtin.command:
      cmd: authselect enable-feature with-pwhistory
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("with-pwhistory")

  - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  - result_authselect_available_features.stdout is search("with-pwhistory")
  tags:
  - CCE-89749-6
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - Enable pam_pwhistory.so in appropriate
    PAM files'
  block:

  - name: 'Limit Password Reuse: password-auth - Define the PAM file to be edited
      as a local fact'
    ansible.builtin.set_fact:
      pam_file_path: /etc/pam.d/password-auth

  - name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
      tool'
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: 'Limit Password Reuse: password-auth - Ensure authselect custom profile
      is used if authselect is present'
    block:

    - name: 'Limit Password Reuse: password-auth - Check integrity of authselect current
        profile'
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      failed_when: false

    - name: 'Limit Password Reuse: password-auth - Informative message based on the
        authselect integrity check result'
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: 'Limit Password Reuse: password-auth - Get authselect current profile'
      ansible.builtin.shell:
        cmd: authselect current -r | awk '{ print $1 }'
      register: result_authselect_profile
      changed_when: false
      when:
      - result_authselect_check_cmd is success

    - name: 'Limit Password Reuse: password-auth - Define the current authselect profile
        as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is match("custom/")

    - name: 'Limit Password Reuse: password-auth - Define the new authselect custom
        profile as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: custom/hardening
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Get authselect current features
        to also enable them in the custom profile'
      ansible.builtin.shell:
        cmd: authselect current | tail -n+3 | awk '{ print $2 }'
      register: result_authselect_features
      changed_when: false
      when:
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Check if any custom profile with
        the same name was already created'
      ansible.builtin.stat:
        path: /etc/authselect/{{ authselect_custom_profile }}
      register: result_authselect_custom_profile_present
      changed_when: false
      when:
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
        based on the current profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b {{ authselect_current_profile
          }}
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is not match("^(custom/|local)")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
        based on sssd profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b sssd
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is match("local")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: password-auth - Ensure the authselect custom profile
        is selected'
      ansible.builtin.command:
        cmd: authselect select {{ authselect_custom_profile }}
      register: result_pam_authselect_select_profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: password-auth - Restore the authselect features
        in the custom profile'
      ansible.builtin.command:
        cmd: authselect enable-feature {{ item }}
      loop: '{{ result_authselect_features.stdout_lines }}'
      register: result_pam_authselect_restore_features
      when:
      - result_authselect_profile is not skipped
      - result_authselect_features is not skipped
      - result_pam_authselect_select_profile is not skipped

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: 'Limit Password Reuse: password-auth - Change the PAM file to be edited
        according to the custom authselect profile'
      ansible.builtin.set_fact:
        pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
          | basename }}
    when:
    - result_authselect_present.stat.exists

  - name: 'Limit Password Reuse: password-auth - Define a fact for control already
      filtered in case filters are used'
    ansible.builtin.set_fact:
      pam_module_control: '{{ var_password_pam_remember_control_flag.split(",")[0]
        }}'

  - name: 'Limit Password Reuse: password-auth - Check if expected PAM module line
      is present in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: 'Limit Password Reuse: password-auth - Include or update the PAM module
      line in {{ pam_file_path }}'
    block:

    - name: 'Limit Password Reuse: password-auth - Check if required PAM module line
        is present in {{ pam_file_path }} with different control'
      ansible.builtin.lineinfile:
        path: '{{ pam_file_path }}'
        regexp: ^\s*password\s+.*\s+pam_pwhistory.so\s*
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_line_other_control_present

    - name: 'Limit Password Reuse: password-auth - Ensure the correct control for
        the required PAM module line in {{ pam_file_path }}'
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
        replace: \1{{ pam_module_control }} \2
      register: result_pam_module_edit
      when:
      - result_pam_line_other_control_present.found == 1

    - name: 'Limit Password Reuse: password-auth - Ensure the required PAM module
        line is included in {{ pam_file_path }}'
      ansible.builtin.lineinfile:
        dest: '{{ pam_file_path }}'
        insertafter: ^password.*requisite.*pam_pwquality\.so
        line: password    {{ pam_module_control }}    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0
  when:
  - '"pam" in ansible_facts.packages'
  - |
    (result_authselect_available_features.stdout is defined and result_authselect_available_features.stdout is not search("with-pwhistory")) or result_authselect_available_features is not defined
  tags:
  - CCE-89749-6
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - Check the presence of /etc/security/pwhistory.conf
    file'
  ansible.builtin.stat:
    path: /etc/security/pwhistory.conf
  register: result_pwhistory_conf_check
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-89749-6
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - pam_pwhistory.so parameters are configured
    in /etc/security/pwhistory.conf file'
  block:

  - name: 'Limit Password Reuse: password-auth - Ensure the pam_pwhistory.so remember
      parameter in /etc/security/pwhistory.conf'
    ansible.builtin.lineinfile:
      path: /etc/security/pwhistory.conf
      regexp: ^\s*remember\s*=
      line: remember = {{ var_password_pam_remember }}
      state: present

  - name: 'Limit Password Reuse: password-auth - Ensure the pam_pwhistory.so remember
      parameter is removed from PAM files'
    block:

    - name: 'Limit Password Reuse: password-auth - Check if /etc/pam.d/password-auth
        file is present'
      ansible.builtin.stat:
        path: /etc/pam.d/password-auth
      register: result_pam_file_present

    - name: 'Limit Password Reuse: password-auth - Check the proper remediation for
        the system'
      block:

      - name: 'Limit Password Reuse: password-auth - Define the PAM file to be edited
          as a local fact'
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/password-auth

      - name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
          tool'
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: 'Limit Password Reuse: password-auth - Ensure authselect custom profile
          is used if authselect is present'
        block:

        - name: 'Limit Password Reuse: password-auth - Check integrity of authselect
            current profile'
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          failed_when: false

        - name: 'Limit Password Reuse: password-auth - Informative message based on
            the authselect integrity check result'
          ansible.builtin.assert:
            that:
            - result_authselect_check_cmd.rc == 0
            fail_msg:
            - authselect integrity check failed. Remediation aborted!
            - This remediation could not be applied because an authselect profile
              was not selected or the selected profile is not intact.
            - It is not recommended to manually edit the PAM files when authselect
              tool is available.
            - In cases where the default authselect profile does not cover a specific
              demand, a custom authselect profile is recommended.
            success_msg:
            - authselect integrity check passed

        - name: 'Limit Password Reuse: password-auth - Get authselect current profile'
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: 'Limit Password Reuse: password-auth - Define the current authselect
            profile as a local fact'
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: 'Limit Password Reuse: password-auth - Define the new authselect custom
            profile as a local fact'
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

        - name: 'Limit Password Reuse: password-auth - Get authselect current features
            to also enable them in the custom profile'
          ansible.builtin.shell:
            cmd: authselect current | tail -n+3 | awk '{ print $2 }'
          register: result_authselect_features
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: 'Limit Password Reuse: password-auth - Check if any custom profile
            with the same name was already created'
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - authselect_current_profile is not match("custom/")

        - name: 'Limit Password Reuse: password-auth - Create an authselect custom
            profile based on the current profile'
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: 'Limit Password Reuse: password-auth - Create an authselect custom
            profile based on sssd profile'
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are
            applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: 'Limit Password Reuse: password-auth - Ensure the authselect custom
            profile is selected'
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: 'Limit Password Reuse: password-auth - Restore the authselect features
            in the custom profile'
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are
            applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: 'Limit Password Reuse: password-auth - Change the PAM file to be edited
            according to the custom authselect profile'
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
        when:
        - result_authselect_present.stat.exists

      - name: 'Limit Password Reuse: password-auth - Define a fact for control already
          filtered in case filters are used'
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: 'Limit Password Reuse: password-auth - Ensure the "remember" option
          from "pam_pwhistory.so" is not present in {{ pam_file_path }}'
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwhistory.so.*)\bremember\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal

      - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are
          applied'
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - result_pam_file_present.stat.exists
  when:
  - '"pam" in ansible_facts.packages'
  - result_pwhistory_conf_check.stat.exists
  tags:
  - CCE-89749-6
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - pam_pwhistory.so parameters are configured
    in PAM files'
  block:

  - name: 'Limit Password Reuse: password-auth - Define the PAM file to be edited
      as a local fact'
    ansible.builtin.set_fact:
      pam_file_path: /etc/pam.d/password-auth

  - name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
      tool'
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: 'Limit Password Reuse: password-auth - Ensure authselect custom profile
      is used if authselect is present'
    block:

    - name: 'Limit Password Reuse: password-auth - Check integrity of authselect current
        profile'
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      failed_when: false

    - name: 'Limit Password Reuse: password-auth - Informative message based on the
        authselect integrity check result'
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: 'Limit Password Reuse: password-auth - Get authselect current profile'
      ansible.builtin.shell:
        cmd: authselect current -r | awk '{ print $1 }'
      register: result_authselect_profile
      changed_when: false
      when:
      - result_authselect_check_cmd is success

    - name: 'Limit Password Reuse: password-auth - Define the current authselect profile
        as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is match("custom/")

    - name: 'Limit Password Reuse: password-auth - Define the new authselect custom
        profile as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: custom/hardening
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Get authselect current features
        to also enable them in the custom profile'
      ansible.builtin.shell:
        cmd: authselect current | tail -n+3 | awk '{ print $2 }'
      register: result_authselect_features
      changed_when: false
      when:
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Check if any custom profile with
        the same name was already created'
      ansible.builtin.stat:
        path: /etc/authselect/{{ authselect_custom_profile }}
      register: result_authselect_custom_profile_present
      changed_when: false
      when:
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
        based on the current profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b {{ authselect_current_profile
          }}
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is not match("^(custom/|local)")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
        based on sssd profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b sssd
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is match("local")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: password-auth - Ensure the authselect custom profile
        is selected'
      ansible.builtin.command:
        cmd: authselect select {{ authselect_custom_profile }}
      register: result_pam_authselect_select_profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: password-auth - Restore the authselect features
        in the custom profile'
      ansible.builtin.command:
        cmd: authselect enable-feature {{ item }}
      loop: '{{ result_authselect_features.stdout_lines }}'
      register: result_pam_authselect_restore_features
      when:
      - result_authselect_profile is not skipped
      - result_authselect_features is not skipped
      - result_pam_authselect_select_profile is not skipped

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: 'Limit Password Reuse: password-auth - Change the PAM file to be edited
        according to the custom authselect profile'
      ansible.builtin.set_fact:
        pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
          | basename }}
    when:
    - result_authselect_present.stat.exists

  - name: 'Limit Password Reuse: password-auth - Define a fact for control already
      filtered in case filters are used'
    ansible.builtin.set_fact:
      pam_module_control: requisite

  - name: 'Limit Password Reuse: password-auth - Check if expected PAM module line
      is present in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: 'Limit Password Reuse: password-auth - Include or update the PAM module
      line in {{ pam_file_path }}'
    block:

    - name: 'Limit Password Reuse: password-auth - Check if required PAM module line
        is present in {{ pam_file_path }} with different control'
      ansible.builtin.lineinfile:
        path: '{{ pam_file_path }}'
        regexp: ^\s*password\s+.*\s+pam_pwhistory.so\s*
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_line_other_control_present

    - name: 'Limit Password Reuse: password-auth - Ensure the correct control for
        the required PAM module line in {{ pam_file_path }}'
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
        replace: \1{{ pam_module_control }} \2
      register: result_pam_module_edit
      when:
      - result_pam_line_other_control_present.found == 1

    - name: 'Limit Password Reuse: password-auth - Ensure the required PAM module
        line is included in {{ pam_file_path }}'
      ansible.builtin.lineinfile:
        dest: '{{ pam_file_path }}'
        line: password    {{ pam_module_control }}    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0

  - name: 'Limit Password Reuse: password-auth - Define a fact for control already
      filtered in case filters are used'
    ansible.builtin.set_fact:
      pam_module_control: requisite

  - name: 'Limit Password Reuse: password-auth - Check if the required PAM module
      option is present in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so\s*.*\sremember\b
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_module_accounts_password_pam_pwhistory_remember_password_auth_option_present

  - name: 'Limit Password Reuse: password-auth - Ensure the "remember" PAM option
      for "pam_pwhistory.so" is included in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      backrefs: true
      regexp: ^(\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so.*)
      line: \1 remember={{ var_password_pam_remember }}
      state: present
    register: result_pam_accounts_password_pam_pwhistory_remember_password_auth_add
    when:
    - result_pam_module_accounts_password_pam_pwhistory_remember_password_auth_option_present.found
      == 0

  - name: 'Limit Password Reuse: password-auth - Ensure the required value for "remember"
      PAM option from "pam_pwhistory.so" in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      backrefs: true
      regexp: ^(\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so\s+.*)(remember)=[0-9a-zA-Z]*\s*(.*)
      line: \1\2={{ var_password_pam_remember }} \3
    register: result_pam_accounts_password_pam_pwhistory_remember_password_auth_edit
    when:
    - result_pam_module_accounts_password_pam_pwhistory_remember_password_auth_option_present.found
      > 0

  - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present.stat.exists
    - (result_pam_remember_add is defined and result_pam_remember_add.changed) or
      (result_pam_remember_edit is defined and result_pam_remember_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - not result_pwhistory_conf_check.stat.exists
  tags:
  - CCE-89749-6
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Check pam_pwhistory.so presence in /etc/pam.d/password-auth  oval:ssg-test_accounts_password_pam_pwhistory_remember_password_auth:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_password_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
requisite
required
requisite,required
^\s*password\s+(?:requisite)\s+pam_pwhistory\.so.*$
^\s*password\s+(?:required)\s+pam_pwhistory\.so.*$
/etc/pam.d/password-auth1

Check remember parameter is present and correct in /etc/pam.d/password-auth  oval:ssg-test_accounts_password_pam_pwhistory_remember_password_auth_pamd:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_password_auth_pamd:obj:1 of type textfilecontent54_object
FilepathPatternInstance
24
^\s*password\b.*\bpam_pwhistory\.so\b.*\bremember=([0-9]*).*$
/etc/pam.d/password-auth1

Check the absence of remember parameter in /etc/security/pwhistory.conf  oval:ssg-test_accounts_password_pam_pwhistory_remember_password_auth_no_pwhistory_conf:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_password_auth_param_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\s*remember\s*=\s*([0-9]+)^/etc/security/pwhistory.conf$1

Check remember parameter is absent in /etc/pam.d/password-auth  oval:ssg-test_accounts_password_pam_pwhistory_remember_password_auth_no_pamd:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_password_auth_pamd:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\s*password\b.*\bpam_pwhistory\.so\b.*\bremember=([0-9]*).*$/etc/pam.d/password-auth1

Check remember parameter is present and correct in /etc/security/pwhistory.conf  oval:ssg-test_accounts_password_pam_pwhistory_remember_password_auth_pwhistory_conf:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_password_auth_param_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
24
^\s*remember\s*=\s*([0-9]+)
^/etc/security/pwhistory.conf$1
Limit Password Reuse: system-authxccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth mediumCCE-89776-9

Limit Password Reuse: system-auth

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_pam_pwhistory_remember_system_auth:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89776-9

References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.1.1
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.8
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(f), IA-5(1)(e)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.5
os-srgSRG-OS-000077-GPOS-00045
cis5.3.3.3.1
pcidss48.3.7, 8.3
Description
Do not allow users to reuse recent passwords. This can be accomplished by using the remember option for the pam_pwhistory PAM module.

On systems with newer versions of authselect, the pam_pwhistory PAM module can be enabled via authselect feature:
authselect enable-feature with-pwhistory
Otherwise, it should be enabled using an authselect custom profile.

Newer systems also have the /etc/security/pwhistory.conf file for setting pam_pwhistory module options. This file should be used whenever available. Otherwise, the pam_pwhistory module options can be set in PAM files.

The value for remember option must be equal or greater than 24
Rationale
Preventing re-use of previous passwords helps ensure that a compromised password is not re-used by a user.
Warnings
warning  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report.
warning  Newer versions of authselect contain an authselect feature to easily and properly enable pam_pwhistory.so module. If this feature is not yet available in your system, an authselect custom profile must be used to avoid integrity issues in PAM files.

# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

var_password_pam_remember='24'
var_password_pam_remember_control_flag='requisite,required'


var_password_pam_remember_control_flag="$(echo $var_password_pam_remember_control_flag | cut -d \, -f 1)"

if [ -f /usr/bin/authselect ]; then
    if authselect list-features sssd | grep -q with-pwhistory; then
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi
        authselect enable-feature with-pwhistory

        authselect apply-changes -b
    else
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            # The "local" profile does not contain essential security features required by multiple Benchmarks.
            # If currently used, it is replaced by "sssd", which is the best option in this case.
            if [[ $CURRENT_PROFILE == local ]]; then
                CURRENT_PROFILE="sssd"
            fi
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
        
        if ! grep -qP "^\s*password\s+\$var_password_pam_remember_control_flag\s+pam_pwhistory.so\s*.*" "$PAM_FILE_PATH"; then
            # Line matching group + control + module was not found. Check group + module.
            if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
                # The control is updated only if one single line matches.
                sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1$var_password_pam_remember_control_flag \2/" "$PAM_FILE_PATH"
            else
                LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
                if [ ! -z $LAST_MATCH_LINE ]; then
                    sed -i --follow-symlinks $LAST_MATCH_LINE" a password     $var_password_pam_remember_control_flag    pam_pwhistory.so" "$PAM_FILE_PATH"
                else
                    echo "password    $var_password_pam_remember_control_flag    pam_pwhistory.so" >> "$PAM_FILE_PATH"
                fi
            fi
        fi
    fi
else

    
    if ! grep -qP "^\s*password\s+\$var_password_pam_remember_control_flag\s+pam_pwhistory.so\s*.*" "/etc/pam.d/system-auth"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "/etc/pam.d/system-auth")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1$var_password_pam_remember_control_flag \2/" "/etc/pam.d/system-auth"
        else
            LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "/etc/pam.d/system-auth" | tail -n 1 | cut -d: -f 1)
            if [ ! -z $LAST_MATCH_LINE ]; then
                sed -i --follow-symlinks $LAST_MATCH_LINE" a password     $var_password_pam_remember_control_flag    pam_pwhistory.so" "/etc/pam.d/system-auth"
            else
                echo "password    $var_password_pam_remember_control_flag    pam_pwhistory.so" >> "/etc/pam.d/system-auth"
            fi
        fi
    fi

fi

PWHISTORY_CONF="/etc/security/pwhistory.conf"
if [ -f $PWHISTORY_CONF ]; then
    regex="^\s*remember\s*="
    line="remember = $var_password_pam_remember"
    if ! grep -q $regex $PWHISTORY_CONF; then
        echo $line >> $PWHISTORY_CONF
    else
        sed -i --follow-symlinks 's|^\s*\(remember\s*=\s*\)\(\S\+\)|\1'"$var_password_pam_remember"'|g' $PWHISTORY_CONF
    fi
    if [ -e "/etc/pam.d/system-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/system-auth"
        if [ -f /usr/bin/authselect ]; then
            
            if ! authselect check; then
            echo "
            authselect integrity check failed. Remediation aborted!
            This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
            It is not recommended to manually edit the PAM files when authselect tool is available.
            In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
            exit 1
            fi

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"
                
                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done
                
                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi
        
    if grep -qP "^\s*password\s.*\bpam_pwhistory.so\s.*\bremember\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwhistory.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then
            
            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/system-auth was not found" >&2
    fi
else
    PAM_FILE_PATH="/etc/pam.d/system-auth"
    if [ -f /usr/bin/authselect ]; then
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            # The "local" profile does not contain essential security features required by multiple Benchmarks.
            # If currently used, it is replaced by "sssd", which is the best option in this case.
            if [[ $CURRENT_PROFILE == local ]]; then
                CURRENT_PROFILE="sssd"
            fi
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
    fi
    

    if ! grep -qP "^\s*password\s+requisite\s+pam_pwhistory.so\s*.*" "$PAM_FILE_PATH"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1requisite \2/" "$PAM_FILE_PATH"
        else
            echo "password    requisite    pam_pwhistory.so" >> "$PAM_FILE_PATH"
        fi
    fi
    # Check the option
    if ! grep -qP "^\s*password\s+requisite\s+pam_pwhistory.so\s*.*\sremember\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "/\s*password\s+requisite\s+pam_pwhistory.so.*/ s/$/ remember=$var_password_pam_remember/" "$PAM_FILE_PATH"
    else
        sed -i -E --follow-symlinks "s/(\s*password\s+requisite\s+pam_pwhistory.so\s+.*)(remember=)[[:alnum:]]*\s*(.*)/\1\2$var_password_pam_remember \3/" "$PAM_FILE_PATH"
    fi
    if [ -f /usr/bin/authselect ]; then
        
        authselect apply-changes -b
    fi
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89776-9
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
- name: XCCDF Value var_password_pam_remember # promote to variable
  set_fact:
    var_password_pam_remember: !!str 24
  tags:
    - always
- name: XCCDF Value var_password_pam_remember_control_flag # promote to variable
  set_fact:
    var_password_pam_remember_control_flag: !!str requisite,required
  tags:
    - always

- name: 'Limit Password Reuse: system-auth - Check if system relies on authselect
    tool'
  ansible.builtin.stat:
    path: /usr/bin/authselect
  register: result_authselect_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-89776-9
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - Collect the available authselect features'
  ansible.builtin.command:
    cmd: authselect list-features sssd
  register: result_authselect_available_features
  changed_when: false
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  tags:
  - CCE-89776-9
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - Enable pam_pwhistory.so using authselect
    feature'
  block:

  - name: 'Limit Password Reuse: system-auth - Check integrity of authselect current
      profile'
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: 'Limit Password Reuse: system-auth - Informative message based on the authselect
      integrity check result'
    ansible.builtin.assert:
      that:
      - result_authselect_check_cmd.rc == 0
      fail_msg:
      - authselect integrity check failed. Remediation aborted!
      - This remediation could not be applied because an authselect profile was not
        selected or the selected profile is not intact.
      - It is not recommended to manually edit the PAM files when authselect tool
        is available.
      - In cases where the default authselect profile does not cover a specific demand,
        a custom authselect profile is recommended.
      success_msg:
      - authselect integrity check passed

  - name: 'Limit Password Reuse: system-auth - Get authselect current features'
    ansible.builtin.shell:
      cmd: authselect current | tail -n+3 | awk '{ print $2 }'
    register: result_authselect_features
    changed_when: false
    when:
    - result_authselect_check_cmd is success

  - name: 'Limit Password Reuse: system-auth - Ensure "with-pwhistory" feature is
      enabled using authselect tool'
    ansible.builtin.command:
      cmd: authselect enable-feature with-pwhistory
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("with-pwhistory")

  - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  - result_authselect_available_features.stdout is search("with-pwhistory")
  tags:
  - CCE-89776-9
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - Enable pam_pwhistory.so in appropriate
    PAM files'
  block:

  - name: 'Limit Password Reuse: system-auth - Define the PAM file to be edited as
      a local fact'
    ansible.builtin.set_fact:
      pam_file_path: /etc/pam.d/system-auth

  - name: 'Limit Password Reuse: system-auth - Check if system relies on authselect
      tool'
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: 'Limit Password Reuse: system-auth - Ensure authselect custom profile is
      used if authselect is present'
    block:

    - name: 'Limit Password Reuse: system-auth - Check integrity of authselect current
        profile'
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      failed_when: false

    - name: 'Limit Password Reuse: system-auth - Informative message based on the
        authselect integrity check result'
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: 'Limit Password Reuse: system-auth - Get authselect current profile'
      ansible.builtin.shell:
        cmd: authselect current -r | awk '{ print $1 }'
      register: result_authselect_profile
      changed_when: false
      when:
      - result_authselect_check_cmd is success

    - name: 'Limit Password Reuse: system-auth - Define the current authselect profile
        as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is match("custom/")

    - name: 'Limit Password Reuse: system-auth - Define the new authselect custom
        profile as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: custom/hardening
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is not match("custom/")

    - name: 'Limit Password Reuse: system-auth - Get authselect current features to
        also enable them in the custom profile'
      ansible.builtin.shell:
        cmd: authselect current | tail -n+3 | awk '{ print $2 }'
      register: result_authselect_features
      changed_when: false
      when:
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: system-auth - Check if any custom profile with
        the same name was already created'
      ansible.builtin.stat:
        path: /etc/authselect/{{ authselect_custom_profile }}
      register: result_authselect_custom_profile_present
      changed_when: false
      when:
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: system-auth - Create an authselect custom profile
        based on the current profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b {{ authselect_current_profile
          }}
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is not match("^(custom/|local)")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: system-auth - Create an authselect custom profile
        based on sssd profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b sssd
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is match("local")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: system-auth - Ensure the authselect custom profile
        is selected'
      ansible.builtin.command:
        cmd: authselect select {{ authselect_custom_profile }}
      register: result_pam_authselect_select_profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: system-auth - Restore the authselect features in
        the custom profile'
      ansible.builtin.command:
        cmd: authselect enable-feature {{ item }}
      loop: '{{ result_authselect_features.stdout_lines }}'
      register: result_pam_authselect_restore_features
      when:
      - result_authselect_profile is not skipped
      - result_authselect_features is not skipped
      - result_pam_authselect_select_profile is not skipped

    - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: 'Limit Password Reuse: system-auth - Change the PAM file to be edited
        according to the custom authselect profile'
      ansible.builtin.set_fact:
        pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
          | basename }}
    when:
    - result_authselect_present.stat.exists

  - name: 'Limit Password Reuse: system-auth - Define a fact for control already filtered
      in case filters are used'
    ansible.builtin.set_fact:
      pam_module_control: '{{ var_password_pam_remember_control_flag.split(",")[0]
        }}'

  - name: 'Limit Password Reuse: system-auth - Check if expected PAM module line is
      present in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: 'Limit Password Reuse: system-auth - Include or update the PAM module line
      in {{ pam_file_path }}'
    block:

    - name: 'Limit Password Reuse: system-auth - Check if required PAM module line
        is present in {{ pam_file_path }} with different control'
      ansible.builtin.lineinfile:
        path: '{{ pam_file_path }}'
        regexp: ^\s*password\s+.*\s+pam_pwhistory.so\s*
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_line_other_control_present

    - name: 'Limit Password Reuse: system-auth - Ensure the correct control for the
        required PAM module line in {{ pam_file_path }}'
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
        replace: \1{{ pam_module_control }} \2
      register: result_pam_module_edit
      when:
      - result_pam_line_other_control_present.found == 1

    - name: 'Limit Password Reuse: system-auth - Ensure the required PAM module line
        is included in {{ pam_file_path }}'
      ansible.builtin.lineinfile:
        dest: '{{ pam_file_path }}'
        insertafter: ^password.*requisite.*pam_pwquality\.so
        line: password    {{ pam_module_control }}    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0
  when:
  - '"pam" in ansible_facts.packages'
  - |
    (result_authselect_available_features.stdout is defined and result_authselect_available_features.stdout is not search("with-pwhistory")) or result_authselect_available_features is not defined
  tags:
  - CCE-89776-9
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - Check the presence of /etc/security/pwhistory.conf
    file'
  ansible.builtin.stat:
    path: /etc/security/pwhistory.conf
  register: result_pwhistory_conf_check
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-89776-9
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - pam_pwhistory.so parameters are configured
    in /etc/security/pwhistory.conf file'
  block:

  - name: 'Limit Password Reuse: system-auth - Ensure the pam_pwhistory.so remember
      parameter in /etc/security/pwhistory.conf'
    ansible.builtin.lineinfile:
      path: /etc/security/pwhistory.conf
      regexp: ^\s*remember\s*=
      line: remember = {{ var_password_pam_remember }}
      state: present

  - name: 'Limit Password Reuse: system-auth - Ensure the pam_pwhistory.so remember
      parameter is removed from PAM files'
    block:

    - name: 'Limit Password Reuse: system-auth - Check if /etc/pam.d/system-auth file
        is present'
      ansible.builtin.stat:
        path: /etc/pam.d/system-auth
      register: result_pam_file_present

    - name: 'Limit Password Reuse: system-auth - Check the proper remediation for
        the system'
      block:

      - name: 'Limit Password Reuse: system-auth - Define the PAM file to be edited
          as a local fact'
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/system-auth

      - name: 'Limit Password Reuse: system-auth - Check if system relies on authselect
          tool'
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: 'Limit Password Reuse: system-auth - Ensure authselect custom profile
          is used if authselect is present'
        block:

        - name: 'Limit Password Reuse: system-auth - Check integrity of authselect
            current profile'
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          failed_when: false

        - name: 'Limit Password Reuse: system-auth - Informative message based on
            the authselect integrity check result'
          ansible.builtin.assert:
            that:
            - result_authselect_check_cmd.rc == 0
            fail_msg:
            - authselect integrity check failed. Remediation aborted!
            - This remediation could not be applied because an authselect profile
              was not selected or the selected profile is not intact.
            - It is not recommended to manually edit the PAM files when authselect
              tool is available.
            - In cases where the default authselect profile does not cover a specific
              demand, a custom authselect profile is recommended.
            success_msg:
            - authselect integrity check passed

        - name: 'Limit Password Reuse: system-auth - Get authselect current profile'
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: 'Limit Password Reuse: system-auth - Define the current authselect
            profile as a local fact'
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: 'Limit Password Reuse: system-auth - Define the new authselect custom
            profile as a local fact'
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

        - name: 'Limit Password Reuse: system-auth - Get authselect current features
            to also enable them in the custom profile'
          ansible.builtin.shell:
            cmd: authselect current | tail -n+3 | awk '{ print $2 }'
          register: result_authselect_features
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: 'Limit Password Reuse: system-auth - Check if any custom profile with
            the same name was already created'
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - authselect_current_profile is not match("custom/")

        - name: 'Limit Password Reuse: system-auth - Create an authselect custom profile
            based on the current profile'
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: 'Limit Password Reuse: system-auth - Create an authselect custom profile
            based on sssd profile'
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are
            applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: 'Limit Password Reuse: system-auth - Ensure the authselect custom
            profile is selected'
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: 'Limit Password Reuse: system-auth - Restore the authselect features
            in the custom profile'
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are
            applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: 'Limit Password Reuse: system-auth - Change the PAM file to be edited
            according to the custom authselect profile'
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
        when:
        - result_authselect_present.stat.exists

      - name: 'Limit Password Reuse: system-auth - Define a fact for control already
          filtered in case filters are used'
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: 'Limit Password Reuse: system-auth - Ensure the "remember" option from
          "pam_pwhistory.so" is not present in {{ pam_file_path }}'
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwhistory.so.*)\bremember\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal

      - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - result_pam_file_present.stat.exists
  when:
  - '"pam" in ansible_facts.packages'
  - result_pwhistory_conf_check.stat.exists
  tags:
  - CCE-89776-9
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - pam_pwhistory.so parameters are configured
    in PAM files'
  block:

  - name: 'Limit Password Reuse: system-auth - Define the PAM file to be edited as
      a local fact'
    ansible.builtin.set_fact:
      pam_file_path: /etc/pam.d/system-auth

  - name: 'Limit Password Reuse: system-auth - Check if system relies on authselect
      tool'
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: 'Limit Password Reuse: system-auth - Ensure authselect custom profile is
      used if authselect is present'
    block:

    - name: 'Limit Password Reuse: system-auth - Check integrity of authselect current
        profile'
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      failed_when: false

    - name: 'Limit Password Reuse: system-auth - Informative message based on the
        authselect integrity check result'
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: 'Limit Password Reuse: system-auth - Get authselect current profile'
      ansible.builtin.shell:
        cmd: authselect current -r | awk '{ print $1 }'
      register: result_authselect_profile
      changed_when: false
      when:
      - result_authselect_check_cmd is success

    - name: 'Limit Password Reuse: system-auth - Define the current authselect profile
        as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is match("custom/")

    - name: 'Limit Password Reuse: system-auth - Define the new authselect custom
        profile as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: custom/hardening
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is not match("custom/")

    - name: 'Limit Password Reuse: system-auth - Get authselect current features to
        also enable them in the custom profile'
      ansible.builtin.shell:
        cmd: authselect current | tail -n+3 | awk '{ print $2 }'
      register: result_authselect_features
      changed_when: false
      when:
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: system-auth - Check if any custom profile with
        the same name was already created'
      ansible.builtin.stat:
        path: /etc/authselect/{{ authselect_custom_profile }}
      register: result_authselect_custom_profile_present
      changed_when: false
      when:
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: system-auth - Create an authselect custom profile
        based on the current profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b {{ authselect_current_profile
          }}
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is not match("^(custom/|local)")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: system-auth - Create an authselect custom profile
        based on sssd profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b sssd
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is match("local")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: system-auth - Ensure the authselect custom profile
        is selected'
      ansible.builtin.command:
        cmd: authselect select {{ authselect_custom_profile }}
      register: result_pam_authselect_select_profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: system-auth - Restore the authselect features in
        the custom profile'
      ansible.builtin.command:
        cmd: authselect enable-feature {{ item }}
      loop: '{{ result_authselect_features.stdout_lines }}'
      register: result_pam_authselect_restore_features
      when:
      - result_authselect_profile is not skipped
      - result_authselect_features is not skipped
      - result_pam_authselect_select_profile is not skipped

    - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: 'Limit Password Reuse: system-auth - Change the PAM file to be edited
        according to the custom authselect profile'
      ansible.builtin.set_fact:
        pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
          | basename }}
    when:
    - result_authselect_present.stat.exists

  - name: 'Limit Password Reuse: system-auth - Define a fact for control already filtered
      in case filters are used'
    ansible.builtin.set_fact:
      pam_module_control: requisite

  - name: 'Limit Password Reuse: system-auth - Check if expected PAM module line is
      present in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: 'Limit Password Reuse: system-auth - Include or update the PAM module line
      in {{ pam_file_path }}'
    block:

    - name: 'Limit Password Reuse: system-auth - Check if required PAM module line
        is present in {{ pam_file_path }} with different control'
      ansible.builtin.lineinfile:
        path: '{{ pam_file_path }}'
        regexp: ^\s*password\s+.*\s+pam_pwhistory.so\s*
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_line_other_control_present

    - name: 'Limit Password Reuse: system-auth - Ensure the correct control for the
        required PAM module line in {{ pam_file_path }}'
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
        replace: \1{{ pam_module_control }} \2
      register: result_pam_module_edit
      when:
      - result_pam_line_other_control_present.found == 1

    - name: 'Limit Password Reuse: system-auth - Ensure the required PAM module line
        is included in {{ pam_file_path }}'
      ansible.builtin.lineinfile:
        dest: '{{ pam_file_path }}'
        line: password    {{ pam_module_control }}    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0

  - name: 'Limit Password Reuse: system-auth - Define a fact for control already filtered
      in case filters are used'
    ansible.builtin.set_fact:
      pam_module_control: requisite

  - name: 'Limit Password Reuse: system-auth - Check if the required PAM module option
      is present in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so\s*.*\sremember\b
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_module_accounts_password_pam_pwhistory_remember_system_auth_option_present

  - name: 'Limit Password Reuse: system-auth - Ensure the "remember" PAM option for
      "pam_pwhistory.so" is included in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      backrefs: true
      regexp: ^(\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so.*)
      line: \1 remember={{ var_password_pam_remember }}
      state: present
    register: result_pam_accounts_password_pam_pwhistory_remember_system_auth_add
    when:
    - result_pam_module_accounts_password_pam_pwhistory_remember_system_auth_option_present.found
      == 0

  - name: 'Limit Password Reuse: system-auth - Ensure the required value for "remember"
      PAM option from "pam_pwhistory.so" in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      backrefs: true
      regexp: ^(\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwhistory.so\s+.*)(remember)=[0-9a-zA-Z]*\s*(.*)
      line: \1\2={{ var_password_pam_remember }} \3
    register: result_pam_accounts_password_pam_pwhistory_remember_system_auth_edit
    when:
    - result_pam_module_accounts_password_pam_pwhistory_remember_system_auth_option_present.found
      > 0

  - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present.stat.exists
    - (result_pam_remember_add is defined and result_pam_remember_add.changed) or
      (result_pam_remember_edit is defined and result_pam_remember_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - not result_pwhistory_conf_check.stat.exists
  tags:
  - CCE-89776-9
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Check pam_pwhistory.so presence in /etc/pam.d/system-auth  oval:ssg-test_accounts_password_pam_pwhistory_remember_system_auth:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_system_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
requisite
required
requisite,required
^\s*password\s+(?:requisite)\s+pam_pwhistory\.so.*$
^\s*password\s+(?:required)\s+pam_pwhistory\.so.*$
/etc/pam.d/system-auth1

Check remember parameter is present and correct in /etc/pam.d/system-auth  oval:ssg-test_accounts_password_pam_pwhistory_remember_system_auth_pamd:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_system_auth_pamd:obj:1 of type textfilecontent54_object
FilepathPatternInstance
24
^\s*password\b.*\bpam_pwhistory\.so\b.*\bremember=([0-9]*).*$
/etc/pam.d/system-auth1

Check the absence of remember parameter in /etc/security/pwhistory.conf  oval:ssg-test_accounts_password_pam_pwhistory_remember_system_auth_no_pwhistory_conf:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_system_auth_param_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\s*remember\s*=\s*([0-9]+)^/etc/security/pwhistory.conf$1

Check remember parameter is absent in /etc/pam.d/system-auth  oval:ssg-test_accounts_password_pam_pwhistory_remember_system_auth_no_pamd:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_system_auth_pamd:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\s*password\b.*\bpam_pwhistory\.so\b.*\bremember=([0-9]*).*$/etc/pam.d/system-auth1

Check remember parameter is present and correct in /etc/security/pwhistory.conf  oval:ssg-test_accounts_password_pam_pwhistory_remember_system_auth_pwhistory_conf:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_pam_pwhistory_remember_system_auth_param_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
24
^\s*remember\s*=\s*([0-9]+)
^/etc/security/pwhistory.conf$1
Lock Accounts After Failed Password Attemptsxccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny mediumCCE-87388-5

Lock Accounts After Failed Password Attempts

Rule IDxccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_passwords_pam_faillock_deny:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87388-5

References:
cis-csc1, 12, 15, 16
cjis5.5.3
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.8
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a), AC-7(a)
nist-csfPR.AC-7
osppFIA_AFL.1
pcidssReq-8.1.6
os-srgSRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
anssiR31
cis5.3.3.1.1
pcidss48.3.4, 8.3
Description
This rule configures the system to lock out accounts after a number of incorrect login attempts using pam_faillock.so. pam_faillock.so module requires multiple entries in pam files. These entries must be carefully defined to work as expected. Ensure that the file /etc/security/faillock.conf contains the following entry: deny = <count> Where count should be less than or equal to 5 and greater than 0. In order to avoid errors when manually editing these files, it is recommended to use the appropriate tools, such as authselect or authconfig, depending on the OS version.
Rationale
By limiting the number of failed logon attempts, the risk of unauthorized system access via user password guessing, also known as brute-forcing, is reduced. Limits are imposed by locking the account.
Warnings
warning  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. If the system supports the /etc/security/faillock.conf file, the pam_faillock parameters should be defined in faillock.conf file.

# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

var_accounts_passwords_pam_faillock_deny='5'


if [ -f /usr/bin/authselect ]; then
    if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect enable-feature with-faillock

authselect apply-changes -b
else
    
AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
for pam_file in "${AUTH_FILES[@]}"
do
    if ! grep -qE '^\s*auth\s+required\s+pam_faillock\.so\s+(preauth silent|authfail).*$' "$pam_file" ; then
        sed -i --follow-symlinks '/^auth.*sufficient.*pam_unix\.so.*/i auth        required      pam_faillock.so preauth silent' "$pam_file"
        sed -i --follow-symlinks '/^auth.*required.*pam_deny\.so.*/i auth        required      pam_faillock.so authfail' "$pam_file"
        sed -i --follow-symlinks '/^account.*required.*pam_unix\.so.*/i account     required      pam_faillock.so' "$pam_file"
    fi
    sed -Ei 's/(auth.*)(\[default=die\])(.*pam_faillock\.so)/\1required     \3/g' "$pam_file"
done

fi

AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
SKIP_FAILLOCK_CHECK=false

FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
    regex="^\s*deny\s*="
    line="deny = $var_accounts_passwords_pam_faillock_deny"
    if ! grep -q $regex $FAILLOCK_CONF; then
        echo $line >> $FAILLOCK_CONF
    else
        sed -i --follow-symlinks 's|^\s*\(deny\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_deny"'|g' $FAILLOCK_CONF
    fi
    
    for pam_file in "${AUTH_FILES[@]}"
    do
        if [ -e "$pam_file" ] ; then
            PAM_FILE_PATH="$pam_file"
            if [ -f /usr/bin/authselect ]; then
                
                if ! authselect check; then
                echo "
                authselect integrity check failed. Remediation aborted!
                This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
                It is not recommended to manually edit the PAM files when authselect tool is available.
                In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
                exit 1
                fi

                CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
                # If not already in use, a custom profile is created preserving the enabled features.
                if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                    ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                    # The "local" profile does not contain essential security features required by multiple Benchmarks.
                    # If currently used, it is replaced by "sssd", which is the best option in this case.
                    if [[ $CURRENT_PROFILE == local ]]; then
                        CURRENT_PROFILE="sssd"
                    fi
                    authselect create-profile hardening -b $CURRENT_PROFILE
                    CURRENT_PROFILE="custom/hardening"
                    
                    authselect apply-changes -b --backup=before-hardening-custom-profile
                    authselect select $CURRENT_PROFILE
                    for feature in $ENABLED_FEATURES; do
                        authselect enable-feature $feature;
                    done
                    
                    authselect apply-changes -b --backup=after-hardening-custom-profile
                fi
                PAM_FILE_NAME=$(basename "$pam_file")
                PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

                authselect apply-changes -b
            fi
            
        if grep -qP "^\s*auth\s.*\bpam_faillock.so\s.*\bdeny\b" "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks "s/(.*auth.*pam_faillock.so.*)\bdeny\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
        fi
            if [ -f /usr/bin/authselect ]; then
                
                authselect apply-changes -b
            fi
        else
            echo "$pam_file was not found" >&2
        fi
    done
    
else
    for pam_file in "${AUTH_FILES[@]}"
    do
        if ! grep -qE '^\s*auth.*pam_faillock\.so (preauth|authfail).*deny' "$pam_file"; then
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ deny='"$var_accounts_passwords_pam_faillock_deny"'/' "$pam_file"
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ deny='"$var_accounts_passwords_pam_faillock_deny"'/' "$pam_file"
        else
            sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*silent.*\)\('"deny"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_deny"'\3/' "$pam_file"
            sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"deny"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_deny"'\3/' "$pam_file"
        fi
    done
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87388-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Check if system relies on authselect
    tool
  ansible.builtin.stat:
    path: /usr/bin/authselect
  register: result_authselect_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-87388-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Remediation where authselect
    tool is present
  block:

  - name: Lock Accounts After Failed Password Attempts - Check integrity of authselect
      current profile
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: Lock Accounts After Failed Password Attempts - Informative message based
      on the authselect integrity check result
    ansible.builtin.assert:
      that:
      - result_authselect_check_cmd.rc == 0
      fail_msg:
      - authselect integrity check failed. Remediation aborted!
      - This remediation could not be applied because an authselect profile was not
        selected or the selected profile is not intact.
      - It is not recommended to manually edit the PAM files when authselect tool
        is available.
      - In cases where the default authselect profile does not cover a specific demand,
        a custom authselect profile is recommended.
      success_msg:
      - authselect integrity check passed

  - name: Lock Accounts After Failed Password Attempts - Get authselect current features
    ansible.builtin.shell:
      cmd: authselect current | tail -n+3 | awk '{ print $2 }'
    register: result_authselect_features
    changed_when: false
    when:
    - result_authselect_check_cmd is success

  - name: Lock Accounts After Failed Password Attempts - Ensure "with-faillock" feature
      is enabled using authselect tool
    ansible.builtin.command:
      cmd: authselect enable-feature with-faillock
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("with-faillock")

  - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
      are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  tags:
  - CCE-87388-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Remediation where authselect
    tool is not present
  block:

  - name: Lock Accounts After Failed Password Attempts - Check if pam_faillock.so
      is already enabled
    ansible.builtin.lineinfile:
      path: /etc/pam.d/system-auth
      regexp: .*auth.*pam_faillock\.so (preauth|authfail)
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_faillock_is_enabled

  - name: Lock Accounts After Failed Password Attempts - Enable pam_faillock.so preauth
      editing PAM files
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      line: auth        required      pam_faillock.so preauth
      insertbefore: ^auth.*sufficient.*pam_unix\.so.*
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_is_enabled.found == 0

  - name: Lock Accounts After Failed Password Attempts - Enable pam_faillock.so authfail
      editing PAM files
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      line: auth        required      pam_faillock.so authfail
      insertbefore: ^auth.*required.*pam_deny\.so.*
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_is_enabled.found == 0

  - name: Lock Accounts After Failed Password Attempts - Enable pam_faillock.so account
      section editing PAM files
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      line: account     required      pam_faillock.so
      insertbefore: ^account.*required.*pam_unix\.so.*
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_is_enabled.found == 0
  when:
  - '"pam" in ansible_facts.packages'
  - not result_authselect_present.stat.exists
  tags:
  - CCE-87388-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_passwords_pam_faillock_deny # promote to variable
  set_fact:
    var_accounts_passwords_pam_faillock_deny: !!str 5
  tags:
    - always

- name: Lock Accounts After Failed Password Attempts - Check the presence of /etc/security/faillock.conf
    file
  ansible.builtin.stat:
    path: /etc/security/faillock.conf
  register: result_faillock_conf_check
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-87388-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Ensure the pam_faillock.so
    deny parameter in /etc/security/faillock.conf
  ansible.builtin.lineinfile:
    path: /etc/security/faillock.conf
    regexp: ^\s*deny\s*=
    line: deny = {{ var_accounts_passwords_pam_faillock_deny }}
    state: present
  when:
  - '"pam" in ansible_facts.packages'
  - result_faillock_conf_check.stat.exists
  tags:
  - CCE-87388-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Ensure the pam_faillock.so
    deny parameter not in PAM files
  block:

  - name: Lock Accounts After Failed Password Attempts - Check if /etc/pam.d/system-auth
      file is present
    ansible.builtin.stat:
      path: /etc/pam.d/system-auth
    register: result_pam_file_present

  - name: Lock Accounts After Failed Password Attempts - Check the proper remediation
      for the system
    block:

    - name: Lock Accounts After Failed Password Attempts - Define the PAM file to
        be edited as a local fact
      ansible.builtin.set_fact:
        pam_file_path: /etc/pam.d/system-auth

    - name: Lock Accounts After Failed Password Attempts - Check if system relies
        on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Lock Accounts After Failed Password Attempts - Ensure authselect custom
        profile is used if authselect is present
      block:

      - name: Lock Accounts After Failed Password Attempts - Check integrity of authselect
          current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        failed_when: false

      - name: Lock Accounts After Failed Password Attempts - Informative message based
          on the authselect integrity check result
        ansible.builtin.assert:
          that:
          - result_authselect_check_cmd.rc == 0
          fail_msg:
          - authselect integrity check failed. Remediation aborted!
          - This remediation could not be applied because an authselect profile was
            not selected or the selected profile is not intact.
          - It is not recommended to manually edit the PAM files when authselect tool
            is available.
          - In cases where the default authselect profile does not cover a specific
            demand, a custom authselect profile is recommended.
          success_msg:
          - authselect integrity check passed

      - name: Lock Accounts After Failed Password Attempts - Get authselect current
          profile
        ansible.builtin.shell:
          cmd: authselect current -r | awk '{ print $1 }'
        register: result_authselect_profile
        changed_when: false
        when:
        - result_authselect_check_cmd is success

      - name: Lock Accounts After Failed Password Attempts - Define the current authselect
          profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is match("custom/")

      - name: Lock Accounts After Failed Password Attempts - Define the new authselect
          custom profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: custom/hardening
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is not match("custom/")

      - name: Lock Accounts After Failed Password Attempts - Get authselect current
          features to also enable them in the custom profile
        ansible.builtin.shell:
          cmd: authselect current | tail -n+3 | awk '{ print $2 }'
        register: result_authselect_features
        changed_when: false
        when:
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")

      - name: Lock Accounts After Failed Password Attempts - Check if any custom profile
          with the same name was already created
        ansible.builtin.stat:
          path: /etc/authselect/{{ authselect_custom_profile }}
        register: result_authselect_custom_profile_present
        changed_when: false
        when:
        - authselect_current_profile is not match("custom/")

      - name: Lock Accounts After Failed Password Attempts - Create an authselect
          custom profile based on the current profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b {{ authselect_current_profile
            }}
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is not match("^(custom/|local)")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Lock Accounts After Failed Password Attempts - Create an authselect
          custom profile based on sssd profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b sssd
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is match("local")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Lock Accounts After Failed Password Attempts - Ensure the authselect
          custom profile is selected
        ansible.builtin.command:
          cmd: authselect select {{ authselect_custom_profile }}
        register: result_pam_authselect_select_profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Lock Accounts After Failed Password Attempts - Restore the authselect
          features in the custom profile
        ansible.builtin.command:
          cmd: authselect enable-feature {{ item }}
        loop: '{{ result_authselect_features.stdout_lines }}'
        register: result_pam_authselect_restore_features
        when:
        - result_authselect_profile is not skipped
        - result_authselect_features is not skipped
        - result_pam_authselect_select_profile is not skipped

      - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - result_pam_authselect_restore_features is not skipped

      - name: Lock Accounts After Failed Password Attempts - Change the PAM file to
          be edited according to the custom authselect profile
        ansible.builtin.set_fact:
          pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
            | basename }}
      when:
      - result_authselect_present.stat.exists

    - name: Lock Accounts After Failed Password Attempts - Define a fact for control
        already filtered in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: ''

    - name: Lock Accounts After Failed Password Attempts - Ensure the "deny" option
        from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\bdeny\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
        are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present.stat.exists
      - result_pam_option_removal is changed
    when:
    - result_pam_file_present.stat.exists

  - name: Lock Accounts After Failed Password Attempts - Check if /etc/pam.d/password-auth
      file is present
    ansible.builtin.stat:
      path: /etc/pam.d/password-auth
    register: result_pam_file_present

  - name: Lock Accounts After Failed Password Attempts - Check the proper remediation
      for the system
    block:

    - name: Lock Accounts After Failed Password Attempts - Define the PAM file to
        be edited as a local fact
      ansible.builtin.set_fact:
        pam_file_path: /etc/pam.d/password-auth

    - name: Lock Accounts After Failed Password Attempts - Check if system relies
        on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Lock Accounts After Failed Password Attempts - Ensure authselect custom
        profile is used if authselect is present
      block:

      - name: Lock Accounts After Failed Password Attempts - Check integrity of authselect
          current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        failed_when: false

      - name: Lock Accounts After Failed Password Attempts - Informative message based
          on the authselect integrity check result
        ansible.builtin.assert:
          that:
          - result_authselect_check_cmd.rc == 0
          fail_msg:
          - authselect integrity check failed. Remediation aborted!
          - This remediation could not be applied because an authselect profile was
            not selected or the selected profile is not intact.
          - It is not recommended to manually edit the PAM files when authselect tool
            is available.
          - In cases where the default authselect profile does not cover a specific
            demand, a custom authselect profile is recommended.
          success_msg:
          - authselect integrity check passed

      - name: Lock Accounts After Failed Password Attempts - Get authselect current
          profile
        ansible.builtin.shell:
          cmd: authselect current -r | awk '{ print $1 }'
        register: result_authselect_profile
        changed_when: false
        when:
        - result_authselect_check_cmd is success

      - name: Lock Accounts After Failed Password Attempts - Define the current authselect
          profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is match("custom/")

      - name: Lock Accounts After Failed Password Attempts - Define the new authselect
          custom profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: custom/hardening
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is not match("custom/")

      - name: Lock Accounts After Failed Password Attempts - Get authselect current
          features to also enable them in the custom profile
        ansible.builtin.shell:
          cmd: authselect current | tail -n+3 | awk '{ print $2 }'
        register: result_authselect_features
        changed_when: false
        when:
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")

      - name: Lock Accounts After Failed Password Attempts - Check if any custom profile
          with the same name was already created
        ansible.builtin.stat:
          path: /etc/authselect/{{ authselect_custom_profile }}
        register: result_authselect_custom_profile_present
        changed_when: false
        when:
        - authselect_current_profile is not match("custom/")

      - name: Lock Accounts After Failed Password Attempts - Create an authselect
          custom profile based on the current profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b {{ authselect_current_profile
            }}
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is not match("^(custom/|local)")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Lock Accounts After Failed Password Attempts - Create an authselect
          custom profile based on sssd profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b sssd
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is match("local")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Lock Accounts After Failed Password Attempts - Ensure the authselect
          custom profile is selected
        ansible.builtin.command:
          cmd: authselect select {{ authselect_custom_profile }}
        register: result_pam_authselect_select_profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Lock Accounts After Failed Password Attempts - Restore the authselect
          features in the custom profile
        ansible.builtin.command:
          cmd: authselect enable-feature {{ item }}
        loop: '{{ result_authselect_features.stdout_lines }}'
        register: result_pam_authselect_restore_features
        when:
        - result_authselect_profile is not skipped
        - result_authselect_features is not skipped
        - result_pam_authselect_select_profile is not skipped

      - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - result_pam_authselect_restore_features is not skipped

      - name: Lock Accounts After Failed Password Attempts - Change the PAM file to
          be edited according to the custom authselect profile
        ansible.builtin.set_fact:
          pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
            | basename }}
      when:
      - result_authselect_present.stat.exists

    - name: Lock Accounts After Failed Password Attempts - Define a fact for control
        already filtered in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: ''

    - name: Lock Accounts After Failed Password Attempts - Ensure the "deny" option
        from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\bdeny\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
        are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present.stat.exists
      - result_pam_option_removal is changed
    when:
    - result_pam_file_present.stat.exists
  when:
  - '"pam" in ansible_facts.packages'
  - result_faillock_conf_check.stat.exists
  tags:
  - CCE-87388-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Ensure the pam_faillock.so
    deny parameter in PAM files
  block:

  - name: Lock Accounts After Failed Password Attempts - Check if pam_faillock.so
      deny parameter is already enabled in pam files
    ansible.builtin.lineinfile:
      path: /etc/pam.d/system-auth
      regexp: .*auth.*pam_faillock\.so (preauth|authfail).*deny
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_faillock_deny_parameter_is_present

  - name: Lock Accounts After Failed Password Attempts - Ensure the inclusion of pam_faillock.so
      preauth deny parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)
      line: \1required\3 deny={{ var_accounts_passwords_pam_faillock_deny }}
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_deny_parameter_is_present.found == 0

  - name: Lock Accounts After Failed Password Attempts - Ensure the inclusion of pam_faillock.so
      authfail deny parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)
      line: \1required\3 deny={{ var_accounts_passwords_pam_faillock_deny }}
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_deny_parameter_is_present.found == 0

  - name: Lock Accounts After Failed Password Attempts - Ensure the desired value
      for pam_faillock.so preauth deny parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)(deny)=[0-9]+(.*)
      line: \1required\3\4={{ var_accounts_passwords_pam_faillock_deny }}\5
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_deny_parameter_is_present.found > 0

  - name: Lock Accounts After Failed Password Attempts - Ensure the desired value
      for pam_faillock.so authfail deny parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)(deny)=[0-9]+(.*)
      line: \1required\3\4={{ var_accounts_passwords_pam_faillock_deny }}\5
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_deny_parameter_is_present.found > 0
  when:
  - '"pam" in ansible_facts.packages'
  - not result_faillock_conf_check.stat.exists
  tags:
  - CCE-87388-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

no more that one pam_unix.so is expected in auth section of system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_system_pam_unix_auth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_system_pam_unix_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\s*auth\N+pam_unix\.so/etc/pam.d/system-auth1

no more that one pam_unix.so is expected in auth section of password-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_password_pam_unix_auth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_password_pam_unix_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\s*auth\N+pam_unix\.so/etc/pam.d/password-auth1

One and only one occurrence is expected in auth section of system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_system_pam_faillock_auth:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_system_pam_faillock_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+preauth[\s\S]*^[\s]*auth[\s]+(sufficient|\[(?=.*\bsuccess=done\b)(?=.*?\bnew_authtok_reqd=done\b)(?=.*?\bdefault=ignore\b).*\])[\s]+pam_unix\.so[\s\S]*^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+authfail/etc/pam.d/system-auth1

One and only one occurrence is expected in system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_system_pam_faillock_account:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_system_pam_faillock_account:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\S]*^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_unix\.so/etc/pam.d/system-auth1

One and only one occurrence is expected in auth section of password-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_password_pam_faillock_auth:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_password_pam_faillock_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+preauth[\s\S]*^[\s]*auth[\s]+(sufficient|\[(?=.*\bsuccess=done\b)(?=.*?\bnew_authtok_reqd=done\b)(?=.*?\bdefault=ignore\b).*\])[\s]+pam_unix\.so[\s\S]*^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+authfail/etc/pam.d/password-auth1

One and only one occurrence is expected in password-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_password_pam_faillock_account:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_password_pam_faillock_account:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\S]*^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_unix\.so/etc/pam.d/password-auth1

Check the expected deny value in system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_parameter_pamd_system:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_parameter_pamd_system:obj:1 of type textfilecontent54_object
FilepathPatternInstance
5
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*deny=([0-9]+)
/etc/pam.d/system-auth1

Check the expected deny value in password-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_parameter_pamd_password:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_parameter_pamd_password:obj:1 of type textfilecontent54_object
FilepathPatternInstance
5
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*deny=([0-9]+)
/etc/pam.d/password-auth1

Check the absence of deny parameter in /etc/security/faillock.conf  oval:ssg-test_accounts_passwords_pam_faillock_deny_parameter_no_faillock_conf:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_parameter_faillock_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*deny[\s]*=[\s]*([0-9]+)/etc/security/faillock.conf1

Check the absence of deny parameter in system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_parameter_no_pamd_system:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_parameter_pamd_system:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*deny=([0-9]+)/etc/pam.d/system-auth1

Check the absence of deny parameter in password-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_parameter_no_pamd_password:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_parameter_pamd_password:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*deny=([0-9]+)/etc/pam.d/password-auth1

Check the expected deny value in /etc/security/faillock.conf  oval:ssg-test_accounts_passwords_pam_faillock_deny_parameter_faillock_conf:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_parameter_faillock_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
5
^[\s]*deny[\s]*=[\s]*([0-9]+)
/etc/security/faillock.conf1
Configure the root Account for Failed Password Attemptsxccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny_root mediumCCE-87975-9

Configure the root Account for Failed Password Attempts

Rule IDxccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny_root
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_passwords_pam_faillock_deny_root:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87975-9

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a), AC-7(b), IA-5(c)
nist-csfPR.AC-7
os-srgSRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
anssiR31
cis5.3.3.1.3
Description
This rule configures the system to lock out the root account after a number of incorrect login attempts using pam_faillock.so. pam_faillock.so module requires multiple entries in pam files. These entries must be carefully defined to work as expected. In order to avoid errors when manually editing these files, it is recommended to use the appropriate tools, such as authselect or authconfig, depending on the OS version.
Rationale
By limiting the number of failed logon attempts, the risk of unauthorized system access via user password guessing, also known as brute-forcing, is reduced. Limits are imposed by locking the account.
Warnings
warning  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. If the system supports the /etc/security/faillock.conf file, the pam_faillock parameters should be defined in faillock.conf file.

# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

if [ -f /usr/bin/authselect ]; then
    if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect enable-feature with-faillock

authselect apply-changes -b
else
    
AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
for pam_file in "${AUTH_FILES[@]}"
do
    if ! grep -qE '^\s*auth\s+required\s+pam_faillock\.so\s+(preauth silent|authfail).*$' "$pam_file" ; then
        sed -i --follow-symlinks '/^auth.*sufficient.*pam_unix\.so.*/i auth        required      pam_faillock.so preauth silent' "$pam_file"
        sed -i --follow-symlinks '/^auth.*required.*pam_deny\.so.*/i auth        required      pam_faillock.so authfail' "$pam_file"
        sed -i --follow-symlinks '/^account.*required.*pam_unix\.so.*/i account     required      pam_faillock.so' "$pam_file"
    fi
    sed -Ei 's/(auth.*)(\[default=die\])(.*pam_faillock\.so)/\1required     \3/g' "$pam_file"
done

fi

AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
SKIP_FAILLOCK_CHECK=false

FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
    regex="^\s*even_deny_root"
    line="even_deny_root"
    if ! grep -q $regex $FAILLOCK_CONF; then
        echo $line >> $FAILLOCK_CONF
    fi
    
    for pam_file in "${AUTH_FILES[@]}"
    do
        if [ -e "$pam_file" ] ; then
            PAM_FILE_PATH="$pam_file"
            if [ -f /usr/bin/authselect ]; then
                
                if ! authselect check; then
                echo "
                authselect integrity check failed. Remediation aborted!
                This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
                It is not recommended to manually edit the PAM files when authselect tool is available.
                In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
                exit 1
                fi

                CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
                # If not already in use, a custom profile is created preserving the enabled features.
                if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                    ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                    # The "local" profile does not contain essential security features required by multiple Benchmarks.
                    # If currently used, it is replaced by "sssd", which is the best option in this case.
                    if [[ $CURRENT_PROFILE == local ]]; then
                        CURRENT_PROFILE="sssd"
                    fi
                    authselect create-profile hardening -b $CURRENT_PROFILE
                    CURRENT_PROFILE="custom/hardening"
                    
                    authselect apply-changes -b --backup=before-hardening-custom-profile
                    authselect select $CURRENT_PROFILE
                    for feature in $ENABLED_FEATURES; do
                        authselect enable-feature $feature;
                    done
                    
                    authselect apply-changes -b --backup=after-hardening-custom-profile
                fi
                PAM_FILE_NAME=$(basename "$pam_file")
                PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

                authselect apply-changes -b
            fi
            
        if grep -qP "^\s*auth\s.*\bpam_faillock.so\s.*\beven_deny_root\b" "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks "s/(.*auth.*pam_faillock.so.*)\beven_deny_root\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
        fi
            if [ -f /usr/bin/authselect ]; then
                
                authselect apply-changes -b
            fi
        else
            echo "$pam_file was not found" >&2
        fi
    done
    
else
    for pam_file in "${AUTH_FILES[@]}"
    do
        if ! grep -qE '^\s*auth.*pam_faillock\.so (preauth|authfail).*even_deny_root' "$pam_file"; then
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ even_deny_root/' "$pam_file"
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ even_deny_root/' "$pam_file"
        fi
    done
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87975-9
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_faillock_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account for Failed Password Attempts - Check if system
    relies on authselect tool
  ansible.builtin.stat:
    path: /usr/bin/authselect
  register: result_authselect_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-87975-9
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_faillock_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account for Failed Password Attempts - Remediation where
    authselect tool is present
  block:

  - name: Configure the root Account for Failed Password Attempts - Check integrity
      of authselect current profile
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: Configure the root Account for Failed Password Attempts - Informative message
      based on the authselect integrity check result
    ansible.builtin.assert:
      that:
      - result_authselect_check_cmd.rc == 0
      fail_msg:
      - authselect integrity check failed. Remediation aborted!
      - This remediation could not be applied because an authselect profile was not
        selected or the selected profile is not intact.
      - It is not recommended to manually edit the PAM files when authselect tool
        is available.
      - In cases where the default authselect profile does not cover a specific demand,
        a custom authselect profile is recommended.
      success_msg:
      - authselect integrity check passed

  - name: Configure the root Account for Failed Password Attempts - Get authselect
      current features
    ansible.builtin.shell:
      cmd: authselect current | tail -n+3 | awk '{ print $2 }'
    register: result_authselect_features
    changed_when: false
    when:
    - result_authselect_check_cmd is success

  - name: Configure the root Account for Failed Password Attempts - Ensure "with-faillock"
      feature is enabled using authselect tool
    ansible.builtin.command:
      cmd: authselect enable-feature with-faillock
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("with-faillock")

  - name: Configure the root Account for Failed Password Attempts - Ensure authselect
      changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  tags:
  - CCE-87975-9
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_faillock_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account for Failed Password Attempts - Remediation where
    authselect tool is not present
  block:

  - name: Configure the root Account for Failed Password Attempts - Check if pam_faillock.so
      is already enabled
    ansible.builtin.lineinfile:
      path: /etc/pam.d/system-auth
      regexp: .*auth.*pam_faillock\.so (preauth|authfail)
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_faillock_is_enabled

  - name: Configure the root Account for Failed Password Attempts - Enable pam_faillock.so
      preauth editing PAM files
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      line: auth        required      pam_faillock.so preauth
      insertbefore: ^auth.*sufficient.*pam_unix\.so.*
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_is_enabled.found == 0

  - name: Configure the root Account for Failed Password Attempts - Enable pam_faillock.so
      authfail editing PAM files
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      line: auth        required      pam_faillock.so authfail
      insertbefore: ^auth.*required.*pam_deny\.so.*
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_is_enabled.found == 0

  - name: Configure the root Account for Failed Password Attempts - Enable pam_faillock.so
      account section editing PAM files
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      line: account     required      pam_faillock.so
      insertbefore: ^account.*required.*pam_unix\.so.*
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_is_enabled.found == 0
  when:
  - '"pam" in ansible_facts.packages'
  - not result_authselect_present.stat.exists
  tags:
  - CCE-87975-9
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_faillock_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account for Failed Password Attempts - Check the presence
    of /etc/security/faillock.conf file
  ansible.builtin.stat:
    path: /etc/security/faillock.conf
  register: result_faillock_conf_check
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-87975-9
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_faillock_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account for Failed Password Attempts - Ensure the pam_faillock.so
    even_deny_root parameter in /etc/security/faillock.conf
  ansible.builtin.lineinfile:
    path: /etc/security/faillock.conf
    regexp: ^\s*even_deny_root
    line: even_deny_root
    state: present
  when:
  - '"pam" in ansible_facts.packages'
  - result_faillock_conf_check.stat.exists
  tags:
  - CCE-87975-9
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_faillock_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account for Failed Password Attempts - Ensure the pam_faillock.so
    even_deny_root parameter not in PAM files
  block:

  - name: Configure the root Account for Failed Password Attempts - Check if /etc/pam.d/system-auth
      file is present
    ansible.builtin.stat:
      path: /etc/pam.d/system-auth
    register: result_pam_file_present

  - name: Configure the root Account for Failed Password Attempts - Check the proper
      remediation for the system
    block:

    - name: Configure the root Account for Failed Password Attempts - Define the PAM
        file to be edited as a local fact
      ansible.builtin.set_fact:
        pam_file_path: /etc/pam.d/system-auth

    - name: Configure the root Account for Failed Password Attempts - Check if system
        relies on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Configure the root Account for Failed Password Attempts - Ensure authselect
        custom profile is used if authselect is present
      block:

      - name: Configure the root Account for Failed Password Attempts - Check integrity
          of authselect current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        failed_when: false

      - name: Configure the root Account for Failed Password Attempts - Informative
          message based on the authselect integrity check result
        ansible.builtin.assert:
          that:
          - result_authselect_check_cmd.rc == 0
          fail_msg:
          - authselect integrity check failed. Remediation aborted!
          - This remediation could not be applied because an authselect profile was
            not selected or the selected profile is not intact.
          - It is not recommended to manually edit the PAM files when authselect tool
            is available.
          - In cases where the default authselect profile does not cover a specific
            demand, a custom authselect profile is recommended.
          success_msg:
          - authselect integrity check passed

      - name: Configure the root Account for Failed Password Attempts - Get authselect
          current profile
        ansible.builtin.shell:
          cmd: authselect current -r | awk '{ print $1 }'
        register: result_authselect_profile
        changed_when: false
        when:
        - result_authselect_check_cmd is success

      - name: Configure the root Account for Failed Password Attempts - Define the
          current authselect profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is match("custom/")

      - name: Configure the root Account for Failed Password Attempts - Define the
          new authselect custom profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: custom/hardening
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is not match("custom/")

      - name: Configure the root Account for Failed Password Attempts - Get authselect
          current features to also enable them in the custom profile
        ansible.builtin.shell:
          cmd: authselect current | tail -n+3 | awk '{ print $2 }'
        register: result_authselect_features
        changed_when: false
        when:
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")

      - name: Configure the root Account for Failed Password Attempts - Check if any
          custom profile with the same name was already created
        ansible.builtin.stat:
          path: /etc/authselect/{{ authselect_custom_profile }}
        register: result_authselect_custom_profile_present
        changed_when: false
        when:
        - authselect_current_profile is not match("custom/")

      - name: Configure the root Account for Failed Password Attempts - Create an
          authselect custom profile based on the current profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b {{ authselect_current_profile
            }}
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is not match("^(custom/|local)")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Configure the root Account for Failed Password Attempts - Create an
          authselect custom profile based on sssd profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b sssd
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is match("local")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Configure the root Account for Failed Password Attempts - Ensure authselect
          changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Configure the root Account for Failed Password Attempts - Ensure the
          authselect custom profile is selected
        ansible.builtin.command:
          cmd: authselect select {{ authselect_custom_profile }}
        register: result_pam_authselect_select_profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Configure the root Account for Failed Password Attempts - Restore the
          authselect features in the custom profile
        ansible.builtin.command:
          cmd: authselect enable-feature {{ item }}
        loop: '{{ result_authselect_features.stdout_lines }}'
        register: result_pam_authselect_restore_features
        when:
        - result_authselect_profile is not skipped
        - result_authselect_features is not skipped
        - result_pam_authselect_select_profile is not skipped

      - name: Configure the root Account for Failed Password Attempts - Ensure authselect
          changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - result_pam_authselect_restore_features is not skipped

      - name: Configure the root Account for Failed Password Attempts - Change the
          PAM file to be edited according to the custom authselect profile
        ansible.builtin.set_fact:
          pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
            | basename }}
      when:
      - result_authselect_present.stat.exists

    - name: Configure the root Account for Failed Password Attempts - Define a fact
        for control already filtered in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: ''

    - name: Configure the root Account for Failed Password Attempts - Ensure the "even_deny_root"
        option from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\beven_deny_root\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Configure the root Account for Failed Password Attempts - Ensure authselect
        changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present.stat.exists
      - result_pam_option_removal is changed
    when:
    - result_pam_file_present.stat.exists

  - name: Configure the root Account for Failed Password Attempts - Check if /etc/pam.d/password-auth
      file is present
    ansible.builtin.stat:
      path: /etc/pam.d/password-auth
    register: result_pam_file_present

  - name: Configure the root Account for Failed Password Attempts - Check the proper
      remediation for the system
    block:

    - name: Configure the root Account for Failed Password Attempts - Define the PAM
        file to be edited as a local fact
      ansible.builtin.set_fact:
        pam_file_path: /etc/pam.d/password-auth

    - name: Configure the root Account for Failed Password Attempts - Check if system
        relies on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Configure the root Account for Failed Password Attempts - Ensure authselect
        custom profile is used if authselect is present
      block:

      - name: Configure the root Account for Failed Password Attempts - Check integrity
          of authselect current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        failed_when: false

      - name: Configure the root Account for Failed Password Attempts - Informative
          message based on the authselect integrity check result
        ansible.builtin.assert:
          that:
          - result_authselect_check_cmd.rc == 0
          fail_msg:
          - authselect integrity check failed. Remediation aborted!
          - This remediation could not be applied because an authselect profile was
            not selected or the selected profile is not intact.
          - It is not recommended to manually edit the PAM files when authselect tool
            is available.
          - In cases where the default authselect profile does not cover a specific
            demand, a custom authselect profile is recommended.
          success_msg:
          - authselect integrity check passed

      - name: Configure the root Account for Failed Password Attempts - Get authselect
          current profile
        ansible.builtin.shell:
          cmd: authselect current -r | awk '{ print $1 }'
        register: result_authselect_profile
        changed_when: false
        when:
        - result_authselect_check_cmd is success

      - name: Configure the root Account for Failed Password Attempts - Define the
          current authselect profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is match("custom/")

      - name: Configure the root Account for Failed Password Attempts - Define the
          new authselect custom profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: custom/hardening
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is not match("custom/")

      - name: Configure the root Account for Failed Password Attempts - Get authselect
          current features to also enable them in the custom profile
        ansible.builtin.shell:
          cmd: authselect current | tail -n+3 | awk '{ print $2 }'
        register: result_authselect_features
        changed_when: false
        when:
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")

      - name: Configure the root Account for Failed Password Attempts - Check if any
          custom profile with the same name was already created
        ansible.builtin.stat:
          path: /etc/authselect/{{ authselect_custom_profile }}
        register: result_authselect_custom_profile_present
        changed_when: false
        when:
        - authselect_current_profile is not match("custom/")

      - name: Configure the root Account for Failed Password Attempts - Create an
          authselect custom profile based on the current profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b {{ authselect_current_profile
            }}
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is not match("^(custom/|local)")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Configure the root Account for Failed Password Attempts - Create an
          authselect custom profile based on sssd profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b sssd
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is match("local")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Configure the root Account for Failed Password Attempts - Ensure authselect
          changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Configure the root Account for Failed Password Attempts - Ensure the
          authselect custom profile is selected
        ansible.builtin.command:
          cmd: authselect select {{ authselect_custom_profile }}
        register: result_pam_authselect_select_profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Configure the root Account for Failed Password Attempts - Restore the
          authselect features in the custom profile
        ansible.builtin.command:
          cmd: authselect enable-feature {{ item }}
        loop: '{{ result_authselect_features.stdout_lines }}'
        register: result_pam_authselect_restore_features
        when:
        - result_authselect_profile is not skipped
        - result_authselect_features is not skipped
        - result_pam_authselect_select_profile is not skipped

      - name: Configure the root Account for Failed Password Attempts - Ensure authselect
          changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - result_pam_authselect_restore_features is not skipped

      - name: Configure the root Account for Failed Password Attempts - Change the
          PAM file to be edited according to the custom authselect profile
        ansible.builtin.set_fact:
          pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
            | basename }}
      when:
      - result_authselect_present.stat.exists

    - name: Configure the root Account for Failed Password Attempts - Define a fact
        for control already filtered in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: ''

    - name: Configure the root Account for Failed Password Attempts - Ensure the "even_deny_root"
        option from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\beven_deny_root\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Configure the root Account for Failed Password Attempts - Ensure authselect
        changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present.stat.exists
      - result_pam_option_removal is changed
    when:
    - result_pam_file_present.stat.exists
  when:
  - '"pam" in ansible_facts.packages'
  - result_faillock_conf_check.stat.exists
  tags:
  - CCE-87975-9
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_faillock_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account for Failed Password Attempts - Ensure the pam_faillock.so
    even_deny_root parameter in PAM files
  block:

  - name: Configure the root Account for Failed Password Attempts - Check if pam_faillock.so
      even_deny_root parameter is already enabled in pam files
    ansible.builtin.lineinfile:
      path: /etc/pam.d/system-auth
      regexp: .*auth.*pam_faillock\.so (preauth|authfail).*even_deny_root
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_faillock_even_deny_root_parameter_is_present

  - name: Configure the root Account for Failed Password Attempts - Ensure the inclusion
      of pam_faillock.so preauth even_deny_root parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)
      line: \1required\3 even_deny_root
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_even_deny_root_parameter_is_present.found == 0

  - name: Configure the root Account for Failed Password Attempts - Ensure the inclusion
      of pam_faillock.so authfail even_deny_root parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)
      line: \1required\3 even_deny_root
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_even_deny_root_parameter_is_present.found == 0
  when:
  - '"pam" in ansible_facts.packages'
  - not result_faillock_conf_check.stat.exists
  tags:
  - CCE-87975-9
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_faillock_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

No more than one pam_unix.so is expected in auth section of system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_system_pam_unix_auth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_system_pam_unix_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth\N+pam_unix\.so^/etc/pam.d/system-auth$1

No more than one pam_unix.so is expected in auth section of password-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_password_pam_unix_auth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_password_pam_unix_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth\N+pam_unix\.so^/etc/pam.d/password-auth$1

One and only one pattern occurrence is expected in auth section of system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_system_pam_faillock_auth:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_system_pam_faillock_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+preauth[\s\S]*^[\s]*auth[\s]+(sufficient|\[(?=.*\bsuccess=done\b)(?=.*?\bnew_authtok_reqd=done\b)(?=.*?\bdefault=ignore\b).*\])[\s]+pam_unix\.so[\s\S]*^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+authfail^/etc/pam.d/system-auth$1

One and only one pattern occurrence is expected in account section of system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_system_pam_faillock_account:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_system_pam_faillock_account:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\S]*^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_unix\.so^/etc/pam.d/system-auth$1

One and only one pattern occurrence is expected in auth section of system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_password_pam_faillock_auth:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_password_pam_faillock_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+preauth[\s\S]*^[\s]*auth[\s]+(sufficient|\[(?=.*\bsuccess=done\b)(?=.*?\bnew_authtok_reqd=done\b)(?=.*?\bdefault=ignore\b).*\])[\s]+pam_unix\.so[\s\S]*^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+authfail^/etc/pam.d/password-auth$1

One and only one pattern occurrence is expected in account section of password-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_password_pam_faillock_account:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_password_pam_faillock_account:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\S]*^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_unix\.so^/etc/pam.d/password-auth$1

Check the expected even_deny_root parameter in system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_parameter_pamd_system:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_parameter_pamd_system:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*even_deny_root^/etc/pam.d/system-auth$1

Check the expected even_deny_root parameter in password-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_parameter_pamd_password:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_parameter_pamd_password:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*even_deny_root^/etc/pam.d/password-auth$1

Check the absence of even_deny_root parameter in /etc/security/faillock.conf  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_parameter_no_faillock_conf:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_parameter_faillock_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*even_deny_root^/etc/security/faillock.conf$1

Check the absence of even_deny_root parameter in system-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_parameter_no_pamd_system:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_parameter_pamd_system:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*even_deny_root^/etc/pam.d/system-auth$1

Check the absence of even_deny_root parameter in password-auth  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_parameter_no_pamd_password:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_parameter_pamd_password:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*even_deny_root^/etc/pam.d/password-auth$1

Check the expected even_deny_root parameter in /etc/security/faillock.conf  oval:ssg-test_accounts_passwords_pam_faillock_deny_root_parameter_faillock_conf:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_deny_root_parameter_faillock_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*even_deny_root^/etc/security/faillock.conf$1
Set Lockout Time for Failed Password Attemptsxccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time mediumCCE-89250-5

Set Lockout Time for Failed Password Attempts

Rule IDxccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_passwords_pam_faillock_unlock_time:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89250-5

References:
cis-csc1, 12, 15, 16
cjis5.5.3
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.8
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a), AC-7(b)
nist-csfPR.AC-7
osppFIA_AFL.1
pcidssReq-8.1.7
os-srgSRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
anssiR31
cis5.3.3.1.2
pcidss48.3.4, 8.3
Description
This rule configures the system to lock out accounts during a specified time period after a number of incorrect login attempts using pam_faillock.so. Ensure that the file /etc/security/faillock.conf contains the following entry: unlock_time=<interval-in-seconds> where interval-in-seconds is 900 or greater. pam_faillock.so module requires multiple entries in pam files. These entries must be carefully defined to work as expected. In order to avoid any errors when manually editing these files, it is recommended to use the appropriate tools, such as authselect or authconfig, depending on the OS version. If unlock_time is set to 0, manual intervention by an administrator is required to unlock a user. This should be done using the faillock tool.
Rationale
By limiting the number of failed logon attempts the risk of unauthorized system access via user password guessing, otherwise known as brute-forcing, is reduced. Limits are imposed by locking the account.
Warnings
warning  If the system supports the new /etc/security/faillock.conf file but the pam_faillock.so parameters are defined directly in /etc/pam.d/system-auth and /etc/pam.d/password-auth, the remediation will migrate the unlock_time parameter to /etc/security/faillock.conf to ensure compatibility with authselect tool. The parameters deny and fail_interval, if used, also have to be migrated by their respective remediation.
warning  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. If the system supports the /etc/security/faillock.conf file, the pam_faillock parameters should be defined in faillock.conf file.

# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

var_accounts_passwords_pam_faillock_unlock_time='900'


if [ -f /usr/bin/authselect ]; then
    if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect enable-feature with-faillock

authselect apply-changes -b
else
    
AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
for pam_file in "${AUTH_FILES[@]}"
do
    if ! grep -qE '^\s*auth\s+required\s+pam_faillock\.so\s+(preauth silent|authfail).*$' "$pam_file" ; then
        sed -i --follow-symlinks '/^auth.*sufficient.*pam_unix\.so.*/i auth        required      pam_faillock.so preauth silent' "$pam_file"
        sed -i --follow-symlinks '/^auth.*required.*pam_deny\.so.*/i auth        required      pam_faillock.so authfail' "$pam_file"
        sed -i --follow-symlinks '/^account.*required.*pam_unix\.so.*/i account     required      pam_faillock.so' "$pam_file"
    fi
    sed -Ei 's/(auth.*)(\[default=die\])(.*pam_faillock\.so)/\1required     \3/g' "$pam_file"
done

fi

AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
SKIP_FAILLOCK_CHECK=false

FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
    regex="^\s*unlock_time\s*="
    line="unlock_time = $var_accounts_passwords_pam_faillock_unlock_time"
    if ! grep -q $regex $FAILLOCK_CONF; then
        echo $line >> $FAILLOCK_CONF
    else
        sed -i --follow-symlinks 's|^\s*\(unlock_time\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_unlock_time"'|g' $FAILLOCK_CONF
    fi
    
    for pam_file in "${AUTH_FILES[@]}"
    do
        if [ -e "$pam_file" ] ; then
            PAM_FILE_PATH="$pam_file"
            if [ -f /usr/bin/authselect ]; then
                
                if ! authselect check; then
                echo "
                authselect integrity check failed. Remediation aborted!
                This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
                It is not recommended to manually edit the PAM files when authselect tool is available.
                In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
                exit 1
                fi

                CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
                # If not already in use, a custom profile is created preserving the enabled features.
                if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                    ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                    # The "local" profile does not contain essential security features required by multiple Benchmarks.
                    # If currently used, it is replaced by "sssd", which is the best option in this case.
                    if [[ $CURRENT_PROFILE == local ]]; then
                        CURRENT_PROFILE="sssd"
                    fi
                    authselect create-profile hardening -b $CURRENT_PROFILE
                    CURRENT_PROFILE="custom/hardening"
                    
                    authselect apply-changes -b --backup=before-hardening-custom-profile
                    authselect select $CURRENT_PROFILE
                    for feature in $ENABLED_FEATURES; do
                        authselect enable-feature $feature;
                    done
                    
                    authselect apply-changes -b --backup=after-hardening-custom-profile
                fi
                PAM_FILE_NAME=$(basename "$pam_file")
                PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

                authselect apply-changes -b
            fi
            
        if grep -qP "^\s*auth\s.*\bpam_faillock.so\s.*\bunlock_time\b" "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks "s/(.*auth.*pam_faillock.so.*)\bunlock_time\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
        fi
            if [ -f /usr/bin/authselect ]; then
                
                authselect apply-changes -b
            fi
        else
            echo "$pam_file was not found" >&2
        fi
    done
    
else
    for pam_file in "${AUTH_FILES[@]}"
    do
        if ! grep -qE '^\s*auth.*pam_faillock\.so (preauth|authfail).*unlock_time' "$pam_file"; then
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ unlock_time='"$var_accounts_passwords_pam_faillock_unlock_time"'/' "$pam_file"
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ unlock_time='"$var_accounts_passwords_pam_faillock_unlock_time"'/' "$pam_file"
        else
            sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*silent.*\)\('"unlock_time"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_unlock_time"'\3/' "$pam_file"
            sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"unlock_time"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_unlock_time"'\3/' "$pam_file"
        fi
    done
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89250-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Check if system relies on
    authselect tool
  ansible.builtin.stat:
    path: /usr/bin/authselect
  register: result_authselect_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-89250-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Remediation where authselect
    tool is present
  block:

  - name: Set Lockout Time for Failed Password Attempts - Check integrity of authselect
      current profile
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: Set Lockout Time for Failed Password Attempts - Informative message based
      on the authselect integrity check result
    ansible.builtin.assert:
      that:
      - result_authselect_check_cmd.rc == 0
      fail_msg:
      - authselect integrity check failed. Remediation aborted!
      - This remediation could not be applied because an authselect profile was not
        selected or the selected profile is not intact.
      - It is not recommended to manually edit the PAM files when authselect tool
        is available.
      - In cases where the default authselect profile does not cover a specific demand,
        a custom authselect profile is recommended.
      success_msg:
      - authselect integrity check passed

  - name: Set Lockout Time for Failed Password Attempts - Get authselect current features
    ansible.builtin.shell:
      cmd: authselect current | tail -n+3 | awk '{ print $2 }'
    register: result_authselect_features
    changed_when: false
    when:
    - result_authselect_check_cmd is success

  - name: Set Lockout Time for Failed Password Attempts - Ensure "with-faillock" feature
      is enabled using authselect tool
    ansible.builtin.command:
      cmd: authselect enable-feature with-faillock
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("with-faillock")

  - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
      are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  tags:
  - CCE-89250-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Remediation where authselect
    tool is not present
  block:

  - name: Set Lockout Time for Failed Password Attempts - Check if pam_faillock.so
      is already enabled
    ansible.builtin.lineinfile:
      path: /etc/pam.d/system-auth
      regexp: .*auth.*pam_faillock\.so (preauth|authfail)
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_faillock_is_enabled

  - name: Set Lockout Time for Failed Password Attempts - Enable pam_faillock.so preauth
      editing PAM files
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      line: auth        required      pam_faillock.so preauth
      insertbefore: ^auth.*sufficient.*pam_unix\.so.*
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_is_enabled.found == 0

  - name: Set Lockout Time for Failed Password Attempts - Enable pam_faillock.so authfail
      editing PAM files
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      line: auth        required      pam_faillock.so authfail
      insertbefore: ^auth.*required.*pam_deny\.so.*
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_is_enabled.found == 0

  - name: Set Lockout Time for Failed Password Attempts - Enable pam_faillock.so account
      section editing PAM files
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      line: account     required      pam_faillock.so
      insertbefore: ^account.*required.*pam_unix\.so.*
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_is_enabled.found == 0
  when:
  - '"pam" in ansible_facts.packages'
  - not result_authselect_present.stat.exists
  tags:
  - CCE-89250-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_passwords_pam_faillock_unlock_time # promote to variable
  set_fact:
    var_accounts_passwords_pam_faillock_unlock_time: !!str 900
  tags:
    - always

- name: Set Lockout Time for Failed Password Attempts - Check the presence of /etc/security/faillock.conf
    file
  ansible.builtin.stat:
    path: /etc/security/faillock.conf
  register: result_faillock_conf_check
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-89250-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Ensure the pam_faillock.so
    unlock_time parameter in /etc/security/faillock.conf
  ansible.builtin.lineinfile:
    path: /etc/security/faillock.conf
    regexp: ^\s*unlock_time\s*=
    line: unlock_time = {{ var_accounts_passwords_pam_faillock_unlock_time }}
    state: present
  when:
  - '"pam" in ansible_facts.packages'
  - result_faillock_conf_check.stat.exists
  tags:
  - CCE-89250-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Ensure the pam_faillock.so
    unlock_time parameter not in PAM files
  block:

  - name: Set Lockout Time for Failed Password Attempts - Check if /etc/pam.d/system-auth
      file is present
    ansible.builtin.stat:
      path: /etc/pam.d/system-auth
    register: result_pam_file_present

  - name: Set Lockout Time for Failed Password Attempts - Check the proper remediation
      for the system
    block:

    - name: Set Lockout Time for Failed Password Attempts - Define the PAM file to
        be edited as a local fact
      ansible.builtin.set_fact:
        pam_file_path: /etc/pam.d/system-auth

    - name: Set Lockout Time for Failed Password Attempts - Check if system relies
        on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Set Lockout Time for Failed Password Attempts - Ensure authselect custom
        profile is used if authselect is present
      block:

      - name: Set Lockout Time for Failed Password Attempts - Check integrity of authselect
          current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        failed_when: false

      - name: Set Lockout Time for Failed Password Attempts - Informative message
          based on the authselect integrity check result
        ansible.builtin.assert:
          that:
          - result_authselect_check_cmd.rc == 0
          fail_msg:
          - authselect integrity check failed. Remediation aborted!
          - This remediation could not be applied because an authselect profile was
            not selected or the selected profile is not intact.
          - It is not recommended to manually edit the PAM files when authselect tool
            is available.
          - In cases where the default authselect profile does not cover a specific
            demand, a custom authselect profile is recommended.
          success_msg:
          - authselect integrity check passed

      - name: Set Lockout Time for Failed Password Attempts - Get authselect current
          profile
        ansible.builtin.shell:
          cmd: authselect current -r | awk '{ print $1 }'
        register: result_authselect_profile
        changed_when: false
        when:
        - result_authselect_check_cmd is success

      - name: Set Lockout Time for Failed Password Attempts - Define the current authselect
          profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is match("custom/")

      - name: Set Lockout Time for Failed Password Attempts - Define the new authselect
          custom profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: custom/hardening
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is not match("custom/")

      - name: Set Lockout Time for Failed Password Attempts - Get authselect current
          features to also enable them in the custom profile
        ansible.builtin.shell:
          cmd: authselect current | tail -n+3 | awk '{ print $2 }'
        register: result_authselect_features
        changed_when: false
        when:
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")

      - name: Set Lockout Time for Failed Password Attempts - Check if any custom
          profile with the same name was already created
        ansible.builtin.stat:
          path: /etc/authselect/{{ authselect_custom_profile }}
        register: result_authselect_custom_profile_present
        changed_when: false
        when:
        - authselect_current_profile is not match("custom/")

      - name: Set Lockout Time for Failed Password Attempts - Create an authselect
          custom profile based on the current profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b {{ authselect_current_profile
            }}
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is not match("^(custom/|local)")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Set Lockout Time for Failed Password Attempts - Create an authselect
          custom profile based on sssd profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b sssd
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is match("local")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Set Lockout Time for Failed Password Attempts - Ensure the authselect
          custom profile is selected
        ansible.builtin.command:
          cmd: authselect select {{ authselect_custom_profile }}
        register: result_pam_authselect_select_profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Set Lockout Time for Failed Password Attempts - Restore the authselect
          features in the custom profile
        ansible.builtin.command:
          cmd: authselect enable-feature {{ item }}
        loop: '{{ result_authselect_features.stdout_lines }}'
        register: result_pam_authselect_restore_features
        when:
        - result_authselect_profile is not skipped
        - result_authselect_features is not skipped
        - result_pam_authselect_select_profile is not skipped

      - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - result_pam_authselect_restore_features is not skipped

      - name: Set Lockout Time for Failed Password Attempts - Change the PAM file
          to be edited according to the custom authselect profile
        ansible.builtin.set_fact:
          pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
            | basename }}
      when:
      - result_authselect_present.stat.exists

    - name: Set Lockout Time for Failed Password Attempts - Define a fact for control
        already filtered in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: ''

    - name: Set Lockout Time for Failed Password Attempts - Ensure the "unlock_time"
        option from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\bunlock_time\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
        are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present.stat.exists
      - result_pam_option_removal is changed
    when:
    - result_pam_file_present.stat.exists

  - name: Set Lockout Time for Failed Password Attempts - Check if /etc/pam.d/password-auth
      file is present
    ansible.builtin.stat:
      path: /etc/pam.d/password-auth
    register: result_pam_file_present

  - name: Set Lockout Time for Failed Password Attempts - Check the proper remediation
      for the system
    block:

    - name: Set Lockout Time for Failed Password Attempts - Define the PAM file to
        be edited as a local fact
      ansible.builtin.set_fact:
        pam_file_path: /etc/pam.d/password-auth

    - name: Set Lockout Time for Failed Password Attempts - Check if system relies
        on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Set Lockout Time for Failed Password Attempts - Ensure authselect custom
        profile is used if authselect is present
      block:

      - name: Set Lockout Time for Failed Password Attempts - Check integrity of authselect
          current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        failed_when: false

      - name: Set Lockout Time for Failed Password Attempts - Informative message
          based on the authselect integrity check result
        ansible.builtin.assert:
          that:
          - result_authselect_check_cmd.rc == 0
          fail_msg:
          - authselect integrity check failed. Remediation aborted!
          - This remediation could not be applied because an authselect profile was
            not selected or the selected profile is not intact.
          - It is not recommended to manually edit the PAM files when authselect tool
            is available.
          - In cases where the default authselect profile does not cover a specific
            demand, a custom authselect profile is recommended.
          success_msg:
          - authselect integrity check passed

      - name: Set Lockout Time for Failed Password Attempts - Get authselect current
          profile
        ansible.builtin.shell:
          cmd: authselect current -r | awk '{ print $1 }'
        register: result_authselect_profile
        changed_when: false
        when:
        - result_authselect_check_cmd is success

      - name: Set Lockout Time for Failed Password Attempts - Define the current authselect
          profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is match("custom/")

      - name: Set Lockout Time for Failed Password Attempts - Define the new authselect
          custom profile as a local fact
        ansible.builtin.set_fact:
          authselect_current_profile: '{{ result_authselect_profile.stdout }}'
          authselect_custom_profile: custom/hardening
        when:
        - result_authselect_profile is not skipped
        - result_authselect_profile.stdout is not match("custom/")

      - name: Set Lockout Time for Failed Password Attempts - Get authselect current
          features to also enable them in the custom profile
        ansible.builtin.shell:
          cmd: authselect current | tail -n+3 | awk '{ print $2 }'
        register: result_authselect_features
        changed_when: false
        when:
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")

      - name: Set Lockout Time for Failed Password Attempts - Check if any custom
          profile with the same name was already created
        ansible.builtin.stat:
          path: /etc/authselect/{{ authselect_custom_profile }}
        register: result_authselect_custom_profile_present
        changed_when: false
        when:
        - authselect_current_profile is not match("custom/")

      - name: Set Lockout Time for Failed Password Attempts - Create an authselect
          custom profile based on the current profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b {{ authselect_current_profile
            }}
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is not match("^(custom/|local)")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Set Lockout Time for Failed Password Attempts - Create an authselect
          custom profile based on sssd profile
        ansible.builtin.command:
          cmd: authselect create-profile hardening -b sssd
        when:
        - result_authselect_check_cmd is success
        - authselect_current_profile is match("local")
        - not result_authselect_custom_profile_present.stat.exists

      - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Set Lockout Time for Failed Password Attempts - Ensure the authselect
          custom profile is selected
        ansible.builtin.command:
          cmd: authselect select {{ authselect_custom_profile }}
        register: result_pam_authselect_select_profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - authselect_current_profile is not match("custom/")
        - authselect_custom_profile is not match(authselect_current_profile)

      - name: Set Lockout Time for Failed Password Attempts - Restore the authselect
          features in the custom profile
        ansible.builtin.command:
          cmd: authselect enable-feature {{ item }}
        loop: '{{ result_authselect_features.stdout_lines }}'
        register: result_pam_authselect_restore_features
        when:
        - result_authselect_profile is not skipped
        - result_authselect_features is not skipped
        - result_pam_authselect_select_profile is not skipped

      - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
        when:
        - result_authselect_check_cmd is success
        - result_authselect_profile is not skipped
        - result_pam_authselect_restore_features is not skipped

      - name: Set Lockout Time for Failed Password Attempts - Change the PAM file
          to be edited according to the custom authselect profile
        ansible.builtin.set_fact:
          pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
            | basename }}
      when:
      - result_authselect_present.stat.exists

    - name: Set Lockout Time for Failed Password Attempts - Define a fact for control
        already filtered in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: ''

    - name: Set Lockout Time for Failed Password Attempts - Ensure the "unlock_time"
        option from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\bunlock_time\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
        are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present.stat.exists
      - result_pam_option_removal is changed
    when:
    - result_pam_file_present.stat.exists
  when:
  - '"pam" in ansible_facts.packages'
  - result_faillock_conf_check.stat.exists
  tags:
  - CCE-89250-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Ensure the pam_faillock.so
    unlock_time parameter in PAM files
  block:

  - name: Set Lockout Time for Failed Password Attempts - Check if pam_faillock.so
      unlock_time parameter is already enabled in pam files
    ansible.builtin.lineinfile:
      path: /etc/pam.d/system-auth
      regexp: .*auth.*pam_faillock\.so (preauth|authfail).*unlock_time
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_faillock_unlock_time_parameter_is_present

  - name: Set Lockout Time for Failed Password Attempts - Ensure the inclusion of
      pam_faillock.so preauth unlock_time parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)
      line: \1required\3 unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time
        }}
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_unlock_time_parameter_is_present.found == 0

  - name: Set Lockout Time for Failed Password Attempts - Ensure the inclusion of
      pam_faillock.so authfail unlock_time parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)
      line: \1required\3 unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time
        }}
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_unlock_time_parameter_is_present.found == 0

  - name: Set Lockout Time for Failed Password Attempts - Ensure the desired value
      for pam_faillock.so preauth unlock_time parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)(unlock_time)=[0-9]+(.*)
      line: \1required\3\4={{ var_accounts_passwords_pam_faillock_unlock_time }}\5
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_unlock_time_parameter_is_present.found > 0

  - name: Set Lockout Time for Failed Password Attempts - Ensure the desired value
      for pam_faillock.so authfail unlock_time parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)(unlock_time)=[0-9]+(.*)
      line: \1required\3\4={{ var_accounts_passwords_pam_faillock_unlock_time }}\5
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_unlock_time_parameter_is_present.found > 0
  when:
  - '"pam" in ansible_facts.packages'
  - not result_faillock_conf_check.stat.exists
  tags:
  - CCE-89250-5
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

no more that one pam_unix.so is expected in auth section of system-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_system_pam_unix_auth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_system_pam_unix_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\s*auth\N+pam_unix\.so/etc/pam.d/system-auth1

no more that one pam_unix.so is expected in auth section of password-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_password_pam_unix_auth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_password_pam_unix_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\s*auth\N+pam_unix\.so/etc/pam.d/password-auth1

One and only one occurrence is expected in auth section of system-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_system_pam_faillock_auth:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_system_pam_faillock_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+preauth[\s\S]*^[\s]*auth[\s]+(sufficient|\[(?=.*\bsuccess=done\b)(?=.*?\bnew_authtok_reqd=done\b)(?=.*?\bdefault=ignore\b).*\])[\s]+pam_unix\.so[\s\S]*^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+authfail/etc/pam.d/system-auth1

One and only one occurrence is expected in system-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_system_pam_faillock_account:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_system_pam_faillock_account:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\S]*^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_unix\.so/etc/pam.d/system-auth1

One and only one occurrence is expected in auth section of password-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_password_pam_faillock_auth:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_password_pam_faillock_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+preauth[\s\S]*^[\s]*auth[\s]+(sufficient|\[(?=.*\bsuccess=done\b)(?=.*?\bnew_authtok_reqd=done\b)(?=.*?\bdefault=ignore\b).*\])[\s]+pam_unix\.so[\s\S]*^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+authfail/etc/pam.d/password-auth1

One and only one occurrence is expected in password-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_password_pam_faillock_account:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_password_pam_faillock_account:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\S]*^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_unix\.so/etc/pam.d/password-auth1

Check the expected unlock_time value in system-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_parameter_pamd_system:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_parameter_pamd_system:obj:1 of type textfilecontent54_object
FilepathPatternInstance
900
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*unlock_time=([0-9]+)
/etc/pam.d/system-auth1

Check the expected unlock_time value in password-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_parameter_pamd_password:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_parameter_pamd_password:obj:1 of type textfilecontent54_object
FilepathPatternInstance
900
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*unlock_time=([0-9]+)
/etc/pam.d/password-auth1

Check the absence of unlock_time parameter in /etc/security/faillock.conf  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_parameter_no_faillock_conf:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_parameter_faillock_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*unlock_time[\s]*=[\s]*([0-9]+)/etc/security/faillock.conf1

Check the absence of unlock_time parameter in system-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_parameter_no_pamd_system:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_parameter_pamd_system:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*unlock_time=([0-9]+)/etc/pam.d/system-auth1

Check the absence of unlock_time parameter in password-auth  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_parameter_no_pamd_password:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_parameter_pamd_password:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*unlock_time=([0-9]+)/etc/pam.d/password-auth1

Check the expected unlock_time value in /etc/security/faillock.conf  oval:ssg-test_accounts_passwords_pam_faillock_unlock_time_parameter_faillock_conf:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_passwords_pam_faillock_unlock_time_parameter_faillock_conf:obj:1 of type textfilecontent54_object
FilepathPatternInstance
900
^[\s]*unlock_time[\s]*=[\s]*([0-9]+)
/etc/security/faillock.conf1
Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary Wordsxccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck mediumCCE-88171-4

Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary Words

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_pam_dictcheck:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88171-4

References:
nistIA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
os-srgSRG-OS-000480-GPOS-00225, SRG-OS-000072-GPOS-00040
cis5.3.3.2.6
Description
The pam_pwquality module's dictcheck check if passwords contains dictionary words. When dictcheck is set to 1 passwords will be checked for dictionary words.
Rationale
Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks.

Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised.

Passwords with dictionary words may be more vulnerable to password-guessing attacks.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q libpwquality; then

var_password_pam_dictcheck='1'



if grep -sq dictcheck /etc/security/pwquality.conf.d/*.conf ; then
    sed -i "/dictcheck/d" /etc/security/pwquality.conf.d/*.conf
fi






# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^dictcheck")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_dictcheck"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^dictcheck\\>" "/etc/security/pwquality.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^dictcheck\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
    if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
    fi
    cce="CCE-88171-4"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
    printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88171-4
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_dictcheck
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_dictcheck # promote to variable
  set_fact:
    var_password_pam_dictcheck: !!str 1
  tags:
    - always

- name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
    Words - Find pwquality.conf.d files
  ansible.builtin.find:
    paths: /etc/security/pwquality.conf.d/
    patterns: '*.conf'
  register: pwquality_conf_d_files
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-88171-4
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_dictcheck
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
    Words - Ensure dictcheck is not set in pwquality.conf.d
  ansible.builtin.lineinfile:
    path: '{{ item.path }}'
    regexp: ^\s*\bdictcheck\b.*
    state: absent
  with_items: '{{ pwquality_conf_d_files.files }}'
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-88171-4
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_dictcheck
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
    Words - Ensure PAM variable dictcheck is set accordingly
  ansible.builtin.lineinfile:
    create: true
    dest: /etc/security/pwquality.conf
    regexp: ^#?\s*dictcheck
    line: dictcheck = {{ var_password_pam_dictcheck }}
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-88171-4
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_dictcheck
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

check the configuration of /etc/pam.d/system-auth  oval:ssg-test_password_pam_pwquality:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/pam.d/system-auth password requisite pam_pwquality.so

check the configuration of /etc/security/pwquality.conf  oval:ssg-test_password_pam_pwquality_dictcheck:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_password_pam_pwquality_dictcheck:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/security/pwquality\.conf(\.d/[^/]+\.conf)?$^\s*dictcheck[\s]*=[\s]*(-?\d+)(?:[\s]|$)1
Ensure PAM Enforces Password Requirements - Minimum Different Charactersxccdf_org.ssgproject.content_rule_accounts_password_pam_difok mediumCCE-90363-3

Ensure PAM Enforces Password Requirements - Minimum Different Characters

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_pam_difok
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_pam_difok:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-90363-3

References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.1.1
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(b), CM-6(a), IA-5(4)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
os-srgSRG-OS-000072-GPOS-00040
cis5.3.3.2.1
Description
The pam_pwquality module's difok parameter sets the number of characters in a password that must not be present in and old password during a password change.

Modify the difok setting in /etc/security/pwquality.conf to equal 2 to require differing characters when changing passwords.
Rationale
Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute–force attacks.

Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised.

Requiring a minimum number of different characters during password changes ensures that newly changed passwords should not resemble previously compromised ones. Note that passwords which are changed on compromised systems will still be compromised, however.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q libpwquality; then

var_password_pam_difok='2'



if grep -sq difok /etc/security/pwquality.conf.d/*.conf ; then
    sed -i "/difok/d" /etc/security/pwquality.conf.d/*.conf
fi






# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^difok")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_difok"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^difok\\>" "/etc/security/pwquality.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^difok\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
    if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
    fi
    cce="CCE-90363-3"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
    printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90363-3
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(b)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_difok
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_difok # promote to variable
  set_fact:
    var_password_pam_difok: !!str 2
  tags:
    - always

- name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
    Find pwquality.conf.d files
  ansible.builtin.find:
    paths: /etc/security/pwquality.conf.d/
    patterns: '*.conf'
  register: pwquality_conf_d_files
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-90363-3
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(b)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_difok
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
    Ensure difok is not set in pwquality.conf.d
  ansible.builtin.lineinfile:
    path: '{{ item.path }}'
    regexp: ^\s*\bdifok\b.*
    state: absent
  with_items: '{{ pwquality_conf_d_files.files }}'
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-90363-3
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(b)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_difok
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
    Ensure PAM variable difok is set accordingly
  ansible.builtin.lineinfile:
    create: true
    dest: /etc/security/pwquality.conf
    regexp: ^#?\s*difok
    line: difok = {{ var_password_pam_difok }}
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-90363-3
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(b)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_difok
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

check the configuration of /etc/pam.d/system-auth  oval:ssg-test_password_pam_pwquality:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/pam.d/system-auth password requisite pam_pwquality.so

check the configuration of /etc/security/pwquality.conf  oval:ssg-test_password_pam_pwquality_difok:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_password_pam_pwquality_difok:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/security/pwquality\.conf(\.d/[^/]+\.conf)?$^\s*difok[\s]*=[\s]*(-?\d+)(?:[\s]|$)1
Ensure PAM Enforces Password Requirements - Enforce for root Userxccdf_org.ssgproject.content_rule_accounts_password_pam_enforce_root mediumCCE-90134-8

Ensure PAM Enforces Password Requirements - Enforce for root User

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_pam_enforce_root
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_pam_enforce_root:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-90134-8

References:
nistIA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
os-srgSRG-OS-000072-GPOS-00040, SRG-OS-000071-GPOS-00039, SRG-OS-000070-GPOS-00038, SRG-OS-000266-GPOS-00101, SRG-OS-000078-GPOS-00046, SRG-OS-000480-GPOS-00225, SRG-OS-000069-GPOS-00037
cis5.3.3.2.7
Description
The pam_pwquality module's enforce_for_root parameter controls requirements for enforcing password complexity for the root user. Enable the enforce_for_root setting in /etc/security/pwquality.conf to require the root user to use complex passwords.
Rationale
Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q libpwquality; then

if [ -e "/etc/security/pwquality.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*enforce_for_root/Id" "/etc/security/pwquality.conf"
else
    touch "/etc/security/pwquality.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/security/pwquality.conf"

cp "/etc/security/pwquality.conf" "/etc/security/pwquality.conf.bak"
# Insert at the end of the file
printf '%s\n' "enforce_for_root" >> "/etc/security/pwquality.conf"
# Clean up after ourselves.
rm "/etc/security/pwquality.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90134-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_enforce_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure PAM Enforces Password Requirements - Enforce for root User
  lineinfile:
    path: /etc/security/pwquality.conf
    create: true
    regexp: ''
    line: enforce_for_root
    state: present
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-90134-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_enforce_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

check the configuration of /etc/pam.d/system-auth  oval:ssg-test_password_pam_pwquality:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/pam.d/system-auth password requisite pam_pwquality.so

check the configuration of /etc/security/pwquality.conf  oval:ssg-test_password_pam_pwquality_enforce_for_root:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_password_pam_pwquality_enforce_for_root:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/security/pwquality\.conf(\.d/[^/]+\.conf)?$^enforce_for_root$1
Set Password Maximum Consecutive Repeating Charactersxccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat mediumCCE-88015-3

Set Password Maximum Consecutive Repeating Characters

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_pam_maxrepeat:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88015-3

References:
cis-csc1, 12, 15, 16, 5
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), CM-6(a), IA-5(4)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
os-srgSRG-OS-000072-GPOS-00040
cis5.3.3.2.4
Description
The pam_pwquality module's maxrepeat parameter controls requirements for consecutive repeating characters. When set to a positive number, it will reject passwords which contain more than that number of consecutive characters. Modify the maxrepeat setting in /etc/security/pwquality.conf to equal 3 to prevent a run of (3 + 1) or more identical characters.
Rationale
Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks.

Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised.

Passwords with excessive repeating characters may be more vulnerable to password-guessing attacks.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q libpwquality; then

var_password_pam_maxrepeat='3'



if grep -sq maxrepeat /etc/security/pwquality.conf.d/*.conf ; then
    sed -i "/maxrepeat/d" /etc/security/pwquality.conf.d/*.conf
fi






# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^maxrepeat")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_maxrepeat"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^maxrepeat\\>" "/etc/security/pwquality.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^maxrepeat\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
    if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
    fi
    cce="CCE-88015-3"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
    printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88015-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_maxrepeat
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_maxrepeat # promote to variable
  set_fact:
    var_password_pam_maxrepeat: !!str 3
  tags:
    - always

- name: Set Password Maximum Consecutive Repeating Characters - Find pwquality.conf.d
    files
  ansible.builtin.find:
    paths: /etc/security/pwquality.conf.d/
    patterns: '*.conf'
  register: pwquality_conf_d_files
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-88015-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_maxrepeat
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Password Maximum Consecutive Repeating Characters - Ensure maxrepeat is
    not set in pwquality.conf.d
  ansible.builtin.lineinfile:
    path: '{{ item.path }}'
    regexp: ^\s*\bmaxrepeat\b.*
    state: absent
  with_items: '{{ pwquality_conf_d_files.files }}'
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-88015-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_maxrepeat
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Password Maximum Consecutive Repeating Characters - Ensure PAM variable
    maxrepeat is set accordingly
  ansible.builtin.lineinfile:
    create: true
    dest: /etc/security/pwquality.conf
    regexp: ^#?\s*maxrepeat
    line: maxrepeat = {{ var_password_pam_maxrepeat }}
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-88015-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_maxrepeat
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

check the configuration of /etc/pam.d/system-auth  oval:ssg-test_password_pam_pwquality:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/pam.d/system-auth password requisite pam_pwquality.so

check the configuration of /etc/security/pwquality.conf  oval:ssg-test_password_pam_pwquality_maxrepeat:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_password_pam_pwquality_maxrepeat:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/security/pwquality\.conf(\.d/[^/]+\.conf)?$^\s*maxrepeat[\s]*=[\s]*(-?\d+)(?:[\s]|$)1
Ensure PAM Enforces Password Requirements - Minimum Different Categoriesxccdf_org.ssgproject.content_rule_accounts_password_pam_minclass mediumCCE-87289-5

Ensure PAM Enforces Password Requirements - Minimum Different Categories

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_pam_minclass
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_pam_minclass:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87289-5

References:
cis-csc1, 12, 15, 16, 5
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
os-srgSRG-OS-000072-GPOS-00040
anssiR68
cis5.3.3.2.3
Description
The pam_pwquality module's minclass parameter controls requirements for usage of different character classes, or types, of character that must exist in a password before it is considered valid. For example, setting this value to three (3) requires that any password must have characters from at least three different categories in order to be approved. The default value is zero (0), meaning there are no required classes. There are four categories available:
* Upper-case characters
* Lower-case characters
* Digits
* Special characters (for example, punctuation)
Modify the minclass setting in /etc/security/pwquality.conf entry to require 4 differing categories of characters when changing passwords.
Rationale
Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks.

Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised.

Requiring a minimum number of character categories makes password guessing attacks more difficult by ensuring a larger search space.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q libpwquality; then

var_password_pam_minclass='4'



if grep -sq minclass /etc/security/pwquality.conf.d/*.conf ; then
    sed -i "/minclass/d" /etc/security/pwquality.conf.d/*.conf
fi






# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^minclass")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_minclass"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^minclass\\>" "/etc/security/pwquality.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^minclass\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
    if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
    fi
    cce="CCE-87289-5"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
    printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87289-5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_minclass
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_minclass # promote to variable
  set_fact:
    var_password_pam_minclass: !!str 4
  tags:
    - always

- name: Ensure PAM Enforces Password Requirements - Minimum Different Categories -
    Find pwquality.conf.d files
  ansible.builtin.find:
    paths: /etc/security/pwquality.conf.d/
    patterns: '*.conf'
  register: pwquality_conf_d_files
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-87289-5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_minclass
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure PAM Enforces Password Requirements - Minimum Different Categories -
    Ensure minclass is not set in pwquality.conf.d
  ansible.builtin.lineinfile:
    path: '{{ item.path }}'
    regexp: ^\s*\bminclass\b.*
    state: absent
  with_items: '{{ pwquality_conf_d_files.files }}'
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-87289-5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_minclass
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure PAM Enforces Password Requirements - Minimum Different Categories -
    Ensure PAM variable minclass is set accordingly
  ansible.builtin.lineinfile:
    create: true
    dest: /etc/security/pwquality.conf
    regexp: ^#?\s*minclass
    line: minclass = {{ var_password_pam_minclass }}
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-87289-5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - accounts_password_pam_minclass
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

check the configuration of /etc/pam.d/system-auth  oval:ssg-test_password_pam_pwquality:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/pam.d/system-auth password requisite pam_pwquality.so

check the configuration of /etc/security/pwquality.conf  oval:ssg-test_password_pam_pwquality_minclass:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_password_pam_pwquality_minclass:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/security/pwquality\.conf(\.d/[^/]+\.conf)?$^\s*minclass[\s]*=[\s]*(-?\d+)(?:[\s]|$)1
Ensure PAM Enforces Password Requirements - Minimum Lengthxccdf_org.ssgproject.content_rule_accounts_password_pam_minlen mediumCCE-87852-0

Ensure PAM Enforces Password Requirements - Minimum Length

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_pam_minlen
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_pam_minlen:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87852-0

References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.1.1
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
osppFMT_SMF_EXT.1
pcidssReq-8.2.3
os-srgSRG-OS-000078-GPOS-00046
anssiR31, R68
cis5.3.3.2.2
pcidss48.3.6, 8.3
Description
The pam_pwquality module's minlen parameter controls requirements for minimum characters required in a password. Add minlen=14 after pam_pwquality to set minimum password length requirements.
Rationale
The shorter the password, the lower the number of possible combinations that need to be tested before the password is compromised.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password length is one factor of several that helps to determine strength and how long it takes to crack a password. Use of more characters in a password helps to exponentially increase the time and/or resources required to compromise the password.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q libpwquality; then

var_password_pam_minlen='14'



if grep -sq minlen /etc/security/pwquality.conf.d/*.conf ; then
    sed -i "/minlen/d" /etc/security/pwquality.conf.d/*.conf
fi






# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^minlen")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_minlen"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^minlen\\>" "/etc/security/pwquality.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^minlen\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
    if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
    fi
    cce="CCE-87852-0"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
    printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87852-0
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_minlen # promote to variable
  set_fact:
    var_password_pam_minlen: !!str 14
  tags:
    - always

- name: Ensure PAM Enforces Password Requirements - Minimum Length - Find pwquality.conf.d
    files
  ansible.builtin.find:
    paths: /etc/security/pwquality.conf.d/
    patterns: '*.conf'
  register: pwquality_conf_d_files
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-87852-0
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure minlen
    is not set in pwquality.conf.d
  ansible.builtin.lineinfile:
    path: '{{ item.path }}'
    regexp: ^\s*\bminlen\b.*
    state: absent
  with_items: '{{ pwquality_conf_d_files.files }}'
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-87852-0
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure PAM variable
    minlen is set accordingly
  ansible.builtin.lineinfile:
    create: true
    dest: /etc/security/pwquality.conf
    regexp: ^#?\s*minlen
    line: minlen = {{ var_password_pam_minlen }}
  when: '"libpwquality" in ansible_facts.packages'
  tags:
  - CCE-87852-0
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

check the configuration of /etc/pam.d/system-auth  oval:ssg-test_password_pam_pwquality:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/pam.d/system-auth password requisite pam_pwquality.so

check the configuration of /etc/security/pwquality.conf  oval:ssg-test_password_pam_pwquality_minlen:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_password_pam_pwquality_minlen:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/security/pwquality\.conf(\.d/[^/]+\.conf)?$^\s*minlen[\s]*=[\s]*(-?\d+)(?:[\s]|$)1
Set Password Hashing Algorithm in /etc/libuser.confxccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_libuserconf mediumCCE-90325-2

Set Password Hashing Algorithm in /etc/libuser.conf

Rule IDxccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_libuserconf
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-90325-2

References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.13.11
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0418, 1055, 1402
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
os-srgSRG-OS-000073-GPOS-00041
cis5.4.1.4
pcidss48.3.2, 8.3
Description
In /etc/libuser.conf, add or correct the following line in its [defaults] section to ensure the system will use the yescrypt algorithm for password hashing:
crypt_style = yescrypt
         
Rationale
Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text.

This setting ensures user and group account administration utilities are configured to store only encrypted representations of passwords. Additionally, the crypt_style configuration option in /etc/libuser.conf ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult.
Set Password Hashing Algorithm in /etc/login.defsxccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_logindefs mediumCCE-89508-6

Set Password Hashing Algorithm in /etc/login.defs

Rule IDxccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_logindefs
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-set_password_hashing_algorithm_logindefs:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89508-6

References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.13.11
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0418, 1055, 1402
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
os-srgSRG-OS-000073-GPOS-00041
cis5.4.1.4
pcidss48.3.2, 8.3
Description
In /etc/login.defs, add or update the following line to ensure the system will use YESCRYPT as the hashing algorithm:
ENCRYPT_METHOD YESCRYPT
         
Rationale
Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text.

Using a stronger hashing algorithm makes password cracking attacks more difficult.
OVAL test results details

The value of ENCRYPT_METHOD should be set appropriately in /etc/login.defs  oval:ssg-test_set_password_hashing_algorithm_logindefs:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-variable_last_encrypt_method_instance_value:var:1YESCRYPT
Set PAM''s Password Hashing Algorithm - password-authxccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_passwordauth mediumCCE-88661-4

Set PAM''s Password Hashing Algorithm - password-auth

Rule IDxccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_passwordauth
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-set_password_hashing_algorithm_passwordauth:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88661-4

References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.13.11
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0418, 1055, 1402
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
os-srgSRG-OS-000073-GPOS-00041, SRG-OS-000120-GPOS-00061
cis5.3.3.4.3
Description
The PAM system service can be configured to only store encrypted representations of passwords. In /etc/pam.d/password-auth, the password section of the file controls which PAM modules to execute during a password change. Set the pam_unix.so module in the password section to include the option yescrypt and no other hashing algorithms as shown below:
password    sufficient    pam_unix.so yescrypt
          other arguments...
         

This will help ensure that new passwords for local users will be stored using the yescrypt algorithm.
Rationale
Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text.

This setting ensures user and group account administration utilities are configured to store only encrypted representations of passwords. Additionally, the crypt_style configuration option in /etc/libuser.conf ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult.
Warnings
warning  The hashing algorithms to be used with pam_unix.so are defined with independent module options. There are at least 7 possible algorithms and likely more algorithms will be introduced along the time. Due the the number of options and its possible combinations, the use of multiple hashing algorithm options may bring unexpected behaviors to the system. For this reason the check will pass only when one hashing algorithm option is defined and is aligned to the "var_password_hashing_algorithm_pam" variable. The remediation will ensure the correct option and remove any other extra hashing algorithm option.
OVAL test results details

check if pam_unix.so hashing algorithm option is correct and specified only once in /etc/pam.d/password-auth  oval:ssg-test_set_password_hashing_algorithm_passwordauth:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/pam.d/password-authpassword sufficient pam_unix.so yescrypt shadow nullok use_authtok
Set PAM''s Password Hashing Algorithmxccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_systemauth mediumCCE-88697-8

Set PAM''s Password Hashing Algorithm

Rule IDxccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_systemauth
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-set_password_hashing_algorithm_systemauth:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88697-8

References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.13.11
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0418, 1055, 1402
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
os-srgSRG-OS-000073-GPOS-00041, SRG-OS-000120-GPOS-00061
anssiR68
cis5.3.3.4.3
pcidss48.3.2, 8.3
Description
The PAM system service can be configured to only store encrypted representations of passwords. In "/etc/pam.d/system-auth", the password section of the file controls which PAM modules to execute during a password change. Set the pam_unix.so module in the password section to include the option yescrypt and no other hashing algorithms as shown below:
password    sufficient    pam_unix.so yescrypt
          other arguments...
         

This will help ensure that new passwords for local users will be stored using the yescrypt algorithm.
Rationale
Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text.

This setting ensures user and group account administration utilities are configured to store only encrypted representations of passwords. Additionally, the crypt_style configuration option in /etc/libuser.conf ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult.
Warnings
warning  The hashing algorithms to be used with pam_unix.so are defined with independent module options. There are at least 7 possible algorithms and likely more algorithms will be introduced along the time. Due the the number of options and its possible combinations, the use of multiple hashing algorithm options may bring unexpected behaviors to the system. For this reason the check will pass only when one hashing algorithm option is defined and is aligned to the "var_password_hashing_algorithm_pam" variable. The remediation will ensure the correct option and remove any other extra hashing algorithm option.
OVAL test results details

check if pam_unix.so hashing algorithm option is correct and specified only once in /etc/pam.d/system-auth  oval:ssg-test_pam_unix_hashing_algorithm_systemauth:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/pam.d/system-authpassword sufficient pam_unix.so yescrypt shadow nullok use_authtok
Install pam_pwquality Packagexccdf_org.ssgproject.content_rule_package_pam_pwquality_installed mediumCCE-90527-3

Install pam_pwquality Package

Rule IDxccdf_org.ssgproject.content_rule_package_pam_pwquality_installed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_pam_pwquality_installed:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-90527-3

References:
os-srgSRG-OS-000480-GPOS-00225
cis5.3.1.3
Description
The libpwquality package can be installed with the following command:
$ sudo dnf install libpwquality
Rationale
Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. "pwquality" enforces complex password construction configuration and has the ability to limit brute-force attacks on the system.
OVAL test results details

package libpwquality is installed  oval:ssg-test_package_libpwquality_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedlibpwqualityx86_64(none)12.el101.4.50:1.4.5-12.el10199e2f91fd431d51libpwquality-0:1.4.5-12.el10.x86_64
Set Existing Passwords Maximum Agexccdf_org.ssgproject.content_rule_accounts_password_set_max_life_existing mediumCCE-87137-6

Set Existing Passwords Maximum Age

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_set_max_life_existing
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_set_max_life_existing:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87137-6

References:
nistIA-5(f), IA-5(1)(d), CM-6(a)
os-srgSRG-OS-000076-GPOS-00044
cis5.4.1.1
pcidss48.3.9, 8.3
Description
Configure non-compliant accounts to enforce a 365-day maximum password lifetime restriction by running the following command:
$ sudo chage -M 365
          USER
         
Rationale
Any password, no matter how complex, can eventually be cracked. Therefore, passwords need to be changed periodically. If the operating system does not limit the lifetime of passwords and force users to change their passwords, there is the risk that the operating system passwords could be compromised.
OVAL test results details

Compares a specific field in /etc/shadow with a specific variable value  oval:ssg-test_accounts_password_set_max_life_existing_password_max_life_existing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_test_accounts_password_set_max_life_existing_password_max_life_existing:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:){2}(\d+):(?:[^:]*:){3}(?:[^:]*)$1

Compares a specific field in /etc/shadow with a specific variable value  oval:ssg-test_accounts_password_set_max_life_existing_password_max_life_existing_minimum:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_test_accounts_password_set_max_life_existing_password_max_life_existing_minimum:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:){2}(\d+):(?:[^:]*:){3}(?:[^:]*)$1

Passwords must have the maximum password age set non-empty in /etc/shadow.  oval:ssg-test_accounts_password_set_max_life_existing_password_max_life_not_empty:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_set_max_life_existing_shadow_password_users_max_life_not_existing:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]+:)(?:[^:]*:){2}():(?:[^:]*:){3}(?:[^:]*)$1
Set Existing Passwords Minimum Agexccdf_org.ssgproject.content_rule_accounts_password_set_min_life_existing mediumCCE-87953-6

Set Existing Passwords Minimum Age

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_set_min_life_existing
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_set_min_life_existing:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87953-6

References:
nistIA-5(f), IA-5(1)(d), CM-6(a)
os-srgSRG-OS-000075-GPOS-00043
cis5.4.1.2
Description
Configure non-compliant accounts to enforce a 24 hours/1 day minimum password lifetime by running the following command:
$ sudo chage -m 1 USER
         
Rationale
Enforcing a minimum password lifetime helps to prevent repeated password changes to defeat the password reuse or history enforcement requirement. If users are allowed to immediately and continually change their password, the password could be repeatedly changed in a short period of time to defeat the organization's policy regarding password reuse.
OVAL test results details

Compares a specific field in /etc/shadow with a specific variable value  oval:ssg-test_accounts_password_set_min_life_existing_password_max_life_existing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_test_accounts_password_set_min_life_existing_password_max_life_existing:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:)(\d+):(?:[^:]*:){4}(?:[^:]*)$1

Compares a specific field in /etc/shadow with a specific variable value  oval:ssg-test_accounts_password_set_min_life_existing_password_max_life_existing_minimum:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_test_accounts_password_set_min_life_existing_password_max_life_existing_minimum:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:)(\d+):(?:[^:]*:){4}(?:[^:]*)$1

Passwords must have the maximum password age set non-empty in /etc/shadow.  oval:ssg-test_accounts_password_set_min_life_existing_password_max_life_not_empty:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_set_min_life_existing_shadow_password_users_max_life_not_existing:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]+:)(?:[^:]*:)():(?:[^:]*:){4}(?:[^:]*)$1
Set Existing Passwords Warning Agexccdf_org.ssgproject.content_rule_accounts_password_set_warn_age_existing mediumCCE-87604-5

Set Existing Passwords Warning Age

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_set_warn_age_existing
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_set_warn_age_existing:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87604-5

References:
nistIA-5(f), IA-5(1)(d), CM-6(a)
cis5.4.1.3
pcidss48.3.9, 8.3
Description
To configure how many days prior to password expiration that a warning will be issued to users, run the command:
$ sudo chage --warndays 7
          USER
         
The DoD requirement is 7, and CIS recommendation is no less than 7 days. This profile requirement is 7.
Rationale
Providing an advance warning that a password will be expiring gives users time to think of a secure password. Users caught unaware may choose a simple password or write it down where it may be discovered.
OVAL test results details

Compares a specific field in /etc/shadow with a specific variable value  oval:ssg-test_accounts_password_set_warn_age_existing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_test_accounts_password_set_warn_age_existing:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:){3}(\d+):(?:[^:]*:){2}(?:[^:]*)$1

Check the inexistence of users with a password defined  oval:ssg-test_accounts_password_set_warn_age_existing_no_pass:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_set_warn_age_existing_no_pass:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:){3}(\d+):(?:[^:]*:){2}(?:[^:]*)$1
Set existing passwords a period of inactivity before they been lockedxccdf_org.ssgproject.content_rule_accounts_set_post_pw_existing mediumCCE-86554-3

Set existing passwords a period of inactivity before they been locked

Rule IDxccdf_org.ssgproject.content_rule_accounts_set_post_pw_existing
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_set_post_pw_existing:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86554-3

References:
cobit5DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.6
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2
iso27001-2013A.12.4.1, A.12.4.3, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nistIA-4(e), AC-2(3), CM-6(a)
nist-csfDE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7
pcidssReq-8.1.4
os-srgSRG-OS-000118-GPOS-00060
cis5.4.1.5
pcidss48.2.6, 8.2
Description
Configure user accounts that have been inactive for over a given period of time to be automatically disabled by running the following command:
$ sudo chage --inactive 30 USER
Rationale
Inactive accounts pose a threat to system security since the users are not logging in to notice failed login attempts or other anomalies.
OVAL test results details

Compares a specific field in /etc/shadow with a specific variable value  oval:ssg-test_accounts_set_post_pw_existing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_test_accounts_set_post_pw_existing:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:){4}(\d+):(?:[^:]*:)(?:[^:]*)$1

Check the inexistence of users with a password defined  oval:ssg-test_accounts_set_post_pw_existing_no_pass:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_set_post_pw_existing_no_pass:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^(?:[^:]*:)(?:[^\!\*:]*:)(?:[^:]*:){4}(\d+):(?:[^:]*:)(?:[^:]*)$1
Verify All Account Password Hashes are Shadowedxccdf_org.ssgproject.content_rule_accounts_password_all_shadowed mediumCCE-87644-1

Verify All Account Password Hashes are Shadowed

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_all_shadowed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_all_shadowed:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87644-1

References:
cis-csc1, 12, 15, 16, 5
cjis5.5.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism1410
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(h), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
cis7.2.1
pcidss48.3.2, 8.3
Description
If any password hashes are stored in /etc/passwd (in the second field, instead of an x or *), the cause of this misconfiguration should be investigated. The account should have its password reset and the hash should be properly stored, or the account should be deleted entirely.
Rationale
The hashes for all user account passwords should be stored in the file /etc/shadow and never in /etc/passwd, which is readable by all users.
OVAL test results details

password hashes are shadowed  oval:ssg-test_accounts_password_all_shadowed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonUsernamePasswordUser idGroup idGcosHome dirLogin shellLast login
truehalt70halt/sbin/sbin/halt0
trueoperator110operator/root/usr/sbin/nologin0
truebin11bin/bin/usr/sbin/nologin0
truegames12100games/usr/games/usr/sbin/nologin0
trueftp1450FTP User/var/ftp/usr/sbin/nologin0
trueroot00Super User/root/bin/bash0
truedaemon22daemon/sbin/usr/sbin/nologin0
truenobody6553465534Kernel Overflow User//usr/sbin/nologin-1
trueyggdrasil999999yggdrasil system user/var/lib/yggdrasil/usr/sbin/nologin0
trueyggdrasil-worker998998yggdrasil worker user//usr/sbin/nologin0
truedbus8181System message bus//sbin/nologin0
truetss5959Account used for TPM access//usr/sbin/nologin0
truesystemd-oom997996systemd Userspace OOM Killer//sbin/nologin0
truepolkitd114114User for polkitd//sbin/nologin0
truedhcpcd996995Minimalistic DHCP client/var/lib/dhcpcd/usr/sbin/nologin0
truesssd995994User for sssd/run/sssd//sbin/nologin0
truesshd7474Privilege-separated SSH/usr/share/empty.sshd/usr/sbin/nologin0
truechrony994993chrony system user/var/lib/chrony/sbin/nologin0
truesystemd-coredump992992systemd Core Dumper//usr/sbin/nologin0
trueec2-user10001000Cloud User/home/ec2-user/bin/bash1753173096
trueshutdown60shutdown/sbin/sbin/shutdown0
truesync50sync/sbin/bin/sync0
truelp47lp/var/spool/lpd/usr/sbin/nologin0
trueadm34adm/var/adm/usr/sbin/nologin0
truemail812mail/var/spool/mail/usr/sbin/nologin0
Ensure all users last password change date is in the pastxccdf_org.ssgproject.content_rule_accounts_password_last_change_is_in_past mediumCCE-90359-1

Ensure all users last password change date is in the past

Rule IDxccdf_org.ssgproject.content_rule_accounts_password_last_change_is_in_past
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_password_last_change_is_in_past:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-90359-1

References:
cis5.4.1.6
pcidss48.3.5, 8.3
Description
All users should have a password change date in the past.
Rationale
If a user recorded password change date is in the future then they could bypass any set password expiration.
Warnings
warning  Automatic remediation is not available, in order to avoid any system disruption.
OVAL test results details

Check if the password last chage time is less than or equal today.  oval:ssg-test_accounts_password_last_change_is_in_past:tst:1  unknown

Following items have been found on the system:
Result of item-state comparisonVar ref
unknownoval:ssg-var_accounts_password_last_change_is_in_past_time_diff:var:1

Check the inexistence of users with a password defined  oval:ssg-test_accounts_password_last_change_is_in_past_no_pass:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_password_last_change_is_in_past:obj:1 of type shadow_object
UsernameFilter
.*oval:ssg-state_accounts_password_all_chage_past_has_no_password:ste:1
All GIDs referenced in /etc/passwd must be defined in /etc/groupxccdf_org.ssgproject.content_rule_gid_passwd_group_same lowCCE-87466-9

All GIDs referenced in /etc/passwd must be defined in /etc/group

Rule IDxccdf_org.ssgproject.content_rule_gid_passwd_group_same
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-gid_passwd_group_same:def:1
Time2025-07-22T08:31:38+00:00
Severitylow
Identifiers:

CCE-87466-9

References:
cis-csc1, 12, 15, 16, 5
cjis5.5.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistIA-2, CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.5.a
os-srgSRG-OS-000104-GPOS-00051
cis7.2.3
pcidss48.2.2, 8.2
Description
Add a group to the system for each GID referenced without a corresponding group.
Rationale
If a user is assigned the Group Identifier (GID) of a group not existing on the system, and a group with the Group Identifier (GID) is subsequently created, the user may have unintended rights to any files associated with the group.
OVAL test results details

Verify all GIDs referenced in /etc/passwd are defined in /etc/group  oval:ssg-test_gid_passwd_group_same:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/passwdroot:x:0:0:
true/etc/passwdbin:x:1:1:
true/etc/passwddaemon:x:2:2:
true/etc/passwdadm:x:3:4:
true/etc/passwdlp:x:4:7:
true/etc/passwdsync:x:5:0:
true/etc/passwdshutdown:x:6:0:
true/etc/passwdhalt:x:7:0:
true/etc/passwdmail:x:8:12:
true/etc/passwdoperator:x:11:0:
true/etc/passwdgames:x:12:100:
true/etc/passwdftp:x:14:50:
true/etc/passwdnobody:x:65534:65534:
true/etc/passwdyggdrasil:x:999:999:
true/etc/passwdyggdrasil-worker:x:998:998:
true/etc/passwddbus:x:81:81:
true/etc/passwdtss:x:59:59:
true/etc/passwdsystemd-oom:x:997:996:
true/etc/passwdpolkitd:x:114:114:
true/etc/passwddhcpcd:x:996:995:
true/etc/passwdsssd:x:995:994:
true/etc/passwdsshd:x:74:74:
true/etc/passwdchrony:x:994:993:
true/etc/passwdsystemd-coredump:x:992:992:
true/etc/passwdec2-user:x:1000:1000:
Prevent Login to Accounts With Empty Passwordxccdf_org.ssgproject.content_rule_no_empty_passwords highCCE-86640-0

Prevent Login to Accounts With Empty Password

Rule IDxccdf_org.ssgproject.content_rule_no_empty_passwords
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-no_empty_passwords:def:1
Time2025-07-22T08:31:38+00:00
Severityhigh
Identifiers:

CCE-86640-0

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2
cobit5APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10
cui3.1.1, 3.1.5
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.18.1.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistIA-5(1)(a), IA-5(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5
osppFIA_UAU.1
pcidssReq-8.2.3
os-srgSRG-OS-000480-GPOS-00227
cis5.3.3.4.1
pcidss48.3.1, 8.3
Description
If an account is configured for password authentication but does not have an assigned password, it may be possible to log into the account without authentication. Remove any instances of the nullok in /etc/pam.d/system-auth and /etc/pam.d/password-auth to prevent logins with empty passwords.
Rationale
If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.
Warnings
warning  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. Note that this rule is not applicable for systems running within a container. Having user with empty password within a container is not considered a risk, because it should not be possible to directly login into a container anyway.

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if [ -f /usr/bin/authselect ]; then
    if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect enable-feature without-nullok

authselect apply-changes -b
else
    
if grep -qP "^\s*auth\s+sufficient\s+pam_unix.so\s.*\bnullok\b" "/etc/pam.d/system-auth"; then
    sed -i -E --follow-symlinks "s/(.*auth.*sufficient.*pam_unix.so.*)\bnullok\b=?[[:alnum:]]*(.*)/\1\2/g" "/etc/pam.d/system-auth"
fi
    
if grep -qP "^\s*password\s+sufficient\s+pam_unix.so\s.*\bnullok\b" "/etc/pam.d/system-auth"; then
    sed -i -E --follow-symlinks "s/(.*password.*sufficient.*pam_unix.so.*)\bnullok\b=?[[:alnum:]]*(.*)/\1\2/g" "/etc/pam.d/system-auth"
fi
    
if grep -qP "^\s*auth\s+sufficient\s+pam_unix.so\s.*\bnullok\b" "/etc/pam.d/password-auth"; then
    sed -i -E --follow-symlinks "s/(.*auth.*sufficient.*pam_unix.so.*)\bnullok\b=?[[:alnum:]]*(.*)/\1\2/g" "/etc/pam.d/password-auth"
fi
    
if grep -qP "^\s*password\s+sufficient\s+pam_unix.so\s.*\bnullok\b" "/etc/pam.d/password-auth"; then
    sed -i -E --follow-symlinks "s/(.*password.*sufficient.*pam_unix.so.*)\bnullok\b=?[[:alnum:]]*(.*)/\1\2/g" "/etc/pam.d/password-auth"
fi
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86640-0
  - CJIS-5.5.2
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.1
  - configure_strategy
  - high_severity
  - low_complexity
  - medium_disruption
  - no_empty_passwords
  - no_reboot_needed

- name: Prevent Login to Accounts With Empty Password - Check if system relies on
    authselect
  ansible.builtin.stat:
    path: /usr/bin/authselect
  register: result_authselect_present
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86640-0
  - CJIS-5.5.2
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.1
  - configure_strategy
  - high_severity
  - low_complexity
  - medium_disruption
  - no_empty_passwords
  - no_reboot_needed

- name: Prevent Login to Accounts With Empty Password - Remediate using authselect
  block:

  - name: Prevent Login to Accounts With Empty Password - Check integrity of authselect
      current profile
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: Prevent Login to Accounts With Empty Password - Informative message based
      on the authselect integrity check result
    ansible.builtin.assert:
      that:
      - result_authselect_check_cmd.rc == 0
      fail_msg:
      - authselect integrity check failed. Remediation aborted!
      - This remediation could not be applied because an authselect profile was not
        selected or the selected profile is not intact.
      - It is not recommended to manually edit the PAM files when authselect tool
        is available.
      - In cases where the default authselect profile does not cover a specific demand,
        a custom authselect profile is recommended.
      success_msg:
      - authselect integrity check passed

  - name: Prevent Login to Accounts With Empty Password - Get authselect current features
    ansible.builtin.shell:
      cmd: authselect current | tail -n+3 | awk '{ print $2 }'
    register: result_authselect_features
    changed_when: false
    when:
    - result_authselect_check_cmd is success

  - name: Prevent Login to Accounts With Empty Password - Ensure "without-nullok"
      feature is enabled using authselect tool
    ansible.builtin.command:
      cmd: authselect enable-feature without-nullok
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("without-nullok")

  - name: Prevent Login to Accounts With Empty Password - Ensure authselect changes
      are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - '"kernel" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  tags:
  - CCE-86640-0
  - CJIS-5.5.2
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.1
  - configure_strategy
  - high_severity
  - low_complexity
  - medium_disruption
  - no_empty_passwords
  - no_reboot_needed

- name: Prevent Login to Accounts With Empty Password - Remediate directly editing
    PAM files
  ansible.builtin.replace:
    dest: '{{ item }}'
    regexp: nullok
  loop:
  - /etc/pam.d/system-auth
  - /etc/pam.d/password-auth
  when:
  - '"kernel" in ansible_facts.packages'
  - not result_authselect_present.stat.exists
  tags:
  - CCE-86640-0
  - CJIS-5.5.2
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.1
  - configure_strategy
  - high_severity
  - low_complexity
  - medium_disruption
  - no_empty_passwords
  - no_reboot_needed

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,%23%20Generated%20by%20authselect%20on%20Sat%20Oct%2027%2014%3A59%3A36%202018%0A%23%20Do%20not%20modify%20this%20file%20manually.%0A%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_env.so%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_faildelay.so%20delay%3D2000000%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_fprintd.so%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20try_first_pass%0Aauth%20%20%20%20%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet_success%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20forward_pass%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3C%201000%20quiet%0Aaccount%20%20%20%20%20%5Bdefault%3Dbad%20success%3Dok%20user_unknown%3Dignore%5D%20pam_sss.so%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_permit.so%0A%0Apassword%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_pwquality.so%20try_first_pass%20local_users_only%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20sha512%20shadow%20try_first_pass%20use_authtok%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20use_authtok%0Apassword%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_keyinit.so%20revoke%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_limits.so%0A-session%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_systemd.so%0Asession%20%20%20%20%20%5Bsuccess%3D1%20default%3Dignore%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20service%20in%20crond%20quiet%20use_uid%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%0A
        mode: 0644
        path: /etc/pam.d/password-auth
        overwrite: true
      - contents:
          source: data:,%23%20Generated%20by%20authselect%20on%20Sat%20Oct%2027%2014%3A59%3A36%202018%0A%23%20Do%20not%20modify%20this%20file%20manually.%0A%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_env.so%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_faildelay.so%20delay%3D2000000%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_fprintd.so%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20try_first_pass%0Aauth%20%20%20%20%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet_success%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20forward_pass%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3C%201000%20quiet%0Aaccount%20%20%20%20%20%5Bdefault%3Dbad%20success%3Dok%20user_unknown%3Dignore%5D%20pam_sss.so%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_permit.so%0A%0Apassword%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_pwquality.so%20try_first_pass%20local_users_only%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20sha512%20shadow%20try_first_pass%20use_authtok%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20use_authtok%0Apassword%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_keyinit.so%20revoke%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_limits.so%0A-session%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_systemd.so%0Asession%20%20%20%20%20%5Bsuccess%3D1%20default%3Dignore%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20service%20in%20crond%20quiet%20use_uid%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%0A
        mode: 0644
        path: /etc/pam.d/system-auth
        overwrite: true
OVAL test results details

make sure nullok is not used in /etc/pam.d/system-auth  oval:ssg-test_no_empty_passwords:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/pam.d/password-auth auth required pam_env.so auth required pam_faildelay.so delay=2000000 auth sufficient pam_unix.so nullok auth required pam_deny.so account required pam_unix.so password requisite pam_pwquality.so password sufficient pam_unix.so yescrypt shadow nullok use_authtok
not evaluated/etc/pam.d/system-auth auth required pam_env.so auth required pam_faildelay.so delay=2000000 auth sufficient pam_unix.so nullok auth required pam_deny.so account required pam_unix.so password requisite pam_pwquality.so password sufficient pam_unix.so yescrypt shadow nullok use_authtok
Ensure There Are No Accounts With Blank or Null Passwordsxccdf_org.ssgproject.content_rule_no_empty_passwords_etc_shadow highCCE-90491-2

Ensure There Are No Accounts With Blank or Null Passwords

Rule IDxccdf_org.ssgproject.content_rule_no_empty_passwords_etc_shadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-no_empty_passwords_etc_shadow:def:1
Time2025-07-22T08:31:38+00:00
Severityhigh
Identifiers:

CCE-90491-2

References:
nistCM-6(b), CM-6.1(iv)
os-srgSRG-OS-000480-GPOS-00227
cis7.2.2
pcidss42.2.2, 2.2
Description
Check the "/etc/shadow" file for blank passwords with the following command:
$ sudo awk -F: '!$2 {print $1}' /etc/shadow
If the command returns any results, this is a finding. Configure all accounts on the system to have a password or lock the account with the following commands: Perform a password reset:
$ sudo passwd [username]
Lock an account:
$ sudo passwd -l [username]
Rationale
If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.
Warnings
warning  Note that this rule is not applicable for systems running within a container. Having user with empty password within a container is not considered a risk, because it should not be possible to directly login into a container anyway.
OVAL test results details

make sure there aren't blank or null passwords in /etc/shadow  oval:ssg-test_no_empty_passwords_etc_shadow:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_no_empty_passwords_etc_shadow:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^[^:]+::.*$1
Verify No .forward Files Existxccdf_org.ssgproject.content_rule_no_forward_files mediumCCE-90050-6

Verify No .forward Files Exist

Rule IDxccdf_org.ssgproject.content_rule_no_forward_files
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-no_forward_files:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-90050-6

References:
cis7.2.9
Description
The .forward file specifies an email address to forward the user's mail to.
Rationale
Use of the .forward file poses a security risk in that sensitive data may be inadvertently transferred outside the organization. The .forward file also poses a risk as it can be used to execute commands that may perform unintended actions.
OVAL test results details

.forward files are not group or world accessible  oval:ssg-test_accounts_users_home_forward_file_existance:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_users_home_forward_file_existance:obj:1 of type file_object
PathFilename
/home/ec2-user\.forward$
Verify No netrc Files Existxccdf_org.ssgproject.content_rule_no_netrc_files mediumCCE-89147-3

Verify No netrc Files Exist

Rule IDxccdf_org.ssgproject.content_rule_no_netrc_files
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-no_netrc_files:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89147-3

References:
cis-csc1, 11, 12, 14, 15, 16, 18, 3, 5
cobit5DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7
iso27001-2013A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R1.3, CIP-003-8 R3, CIP-003-8 R3.1, CIP-003-8 R3.2, CIP-003-8 R3.3, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistIA-5(h), IA-5(1)(c), CM-6(a), IA-5(7)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3
cis7.2.9
Description
The .netrc files contain login information used to auto-login into FTP servers and reside in the user's home directory. These files may contain unencrypted passwords to remote FTP servers making them susceptible to access by unauthorized users and should not be used. Any .netrc files should be removed.
Rationale
Unencrypted passwords for remote FTP servers may be stored in .netrc files.
OVAL test results details

look for .netrc in /home  oval:ssg-test_no_netrc_files_home:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_no_netrc_files_home:obj:1 of type file_object
BehaviorsPathFilename
no value/home^\.netrc$
Verify Only Root Has UID 0xccdf_org.ssgproject.content_rule_accounts_no_uid_except_zero highCCE-87552-6

Verify Only Root Has UID 0

Rule IDxccdf_org.ssgproject.content_rule_accounts_no_uid_except_zero
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_no_uid_except_zero:def:1
Time2025-07-22T08:31:38+00:00
Severityhigh
Identifiers:

CCE-87552-6

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10
cui3.1.1, 3.1.5
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.18.1.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistIA-2, AC-6(5), IA-4(b)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5
pcidssReq-8.5
os-srgSRG-OS-000480-GPOS-00227
cis5.4.2.1
pcidss48.2.1, 8.2
Description
If any account other than root has a UID of 0, this misconfiguration should be investigated and the accounts other than root should be removed or have their UID changed.
If the account is associated with system commands or applications the UID should be changed to one greater than "0" but less than "1000." Otherwise assign a UID greater than "1000" that has not already been assigned.
Rationale
An account has root authority if it has a UID of 0. Multiple accounts with a UID of 0 afford more opportunity for potential intruders to guess a password for a privileged account. Proper configuration of sudo is recommended to afford multiple system administrators access to root privileges in an accountable manner.
OVAL test results details

test that there are no accounts with UID 0 except root in the /etc/passwd file  oval:ssg-test_accounts_no_uid_except_root:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_no_uid_except_root:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/passwd^(?!root:)[^:]*:[^:]*:01
Verify Root Has A Primary GID 0xccdf_org.ssgproject.content_rule_accounts_root_gid_zero highCCE-90244-5

Verify Root Has A Primary GID 0

Rule IDxccdf_org.ssgproject.content_rule_accounts_root_gid_zero
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_root_gid_zero:def:1
Time2025-07-22T08:31:38+00:00
Severityhigh
Identifiers:

CCE-90244-5

References:
pcidssReq-8.1.1
cis5.4.2.2
pcidss48.2.1, 8.2
Description
The root user should have a primary group of 0.
Rationale
To help ensure that root-owned files are not inadvertently exposed to other users.
OVAL test results details

test that the root user has GID 0 in the /etc/passwd file  oval:ssg-test_accounts_root_gid_zero:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/passwdroot:x:0:0:Super User:/root:/bin/bash

test that there are no other accounts with GID 0 except root  oval:ssg-test_accounts_root_gid_zero_no_other_gid_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_root_gid_zero_no_other_gid_0:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/passwd^(?!\b(root|sync|shutdown|halt|operator)\b).+:.+:\d+:0:.+$1
Ensure the Group Used by pam_wheel.so Module Exists on System and is Emptyxccdf_org.ssgproject.content_rule_ensure_pam_wheel_group_empty mediumCCE-89099-6

Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty

Rule IDxccdf_org.ssgproject.content_rule_ensure_pam_wheel_group_empty
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-ensure_pam_wheel_group_empty:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89099-6

References:
cis5.2.7
pcidss42.2.6, 2.2
Description
Ensure that the group sugroup referenced by var_pam_wheel_group_for_su variable and used as value for the pam_wheel.so group option exists and has no members. This empty group used by pam_wheel.so in /etc/pam.d/su ensures that no user can run commands with altered privileges through the su command.
Rationale
The su program allows to run commands with a substitute user and group ID. It is commonly used to run commands as the root user. Limiting access to such command is considered a good security practice.
Warnings
warning  Note that this rule just ensures the group exists and has no members. This rule does not configure pam_wheel.so module. The pam_wheel.so module configuration is accomplished by use_pam_wheel_group_for_su rule.

# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

var_pam_wheel_group_for_su='sugroup'


if ! grep -q "^${var_pam_wheel_group_for_su}:[^:]*:[^:]*:[^:]*" /etc/group; then
    groupadd ${var_pam_wheel_group_for_su}
fi

# group must be empty
gpasswd -M '' ${var_pam_wheel_group_for_su}

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89099-6
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - ensure_pam_wheel_group_empty
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_pam_wheel_group_for_su # promote to variable
  set_fact:
    var_pam_wheel_group_for_su: !!str sugroup
  tags:
    - always

- name: Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty
    - Ensure {{ var_pam_wheel_group_for_su }} Group Exists
  ansible.builtin.group:
    name: '{{ var_pam_wheel_group_for_su }}'
    state: present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-89099-6
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - ensure_pam_wheel_group_empty
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty
    - Ensure {{ var_pam_wheel_group_for_su }} Group is Empty
  ansible.builtin.lineinfile:
    path: /etc/group
    regexp: ^({{ var_pam_wheel_group_for_su }}:[^:]+:[0-9]+:).*$
    line: \1
    backrefs: true
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-89099-6
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - ensure_pam_wheel_group_empty
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

check if group in var_pam_wheel_group_for_su variable used by pam_wheel.so exists  oval:ssg-test_ensure_pam_wheel_group_empty_group_exists:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_ensure_pam_wheel_group_exists:obj:1 of type textfilecontent54_object
FilepathPatternInstance
sugroup
^sugroup:[^:]+:[0-9]+:.*$
/etc/group1

check if group defined by pam_wheel.so group option has no members  oval:ssg-test_ensure_pam_wheel_group_empty_has_no_members:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_ensure_pam_wheel_group_exists:obj:1 of type textfilecontent54_object
FilepathPatternInstance
sugroup
^sugroup:[^:]+:[0-9]+:.*$
/etc/group1
Ensure Authentication Required for Single User Modexccdf_org.ssgproject.content_rule_ensure_root_password_configured mediumCCE-89334-7

Ensure Authentication Required for Single User Mode

Rule IDxccdf_org.ssgproject.content_rule_ensure_root_password_configured
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-ensure_root_password_configured:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89334-7

References:
cis5.4.2.4
pcidss42.2.2, 2.2
Description
Single user mode is used for recovery when the system detects an issue during boot or by manual selection from the bootloader.
Rationale
Requiring authentication in single user mode prevents an unauthorized user from rebooting the system into single user to gain root privileges without credentials.
OVAL test results details

make sure root password is set in /etc/shadow  oval:ssg-test_root_password_etc_shadow:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_root_password_etc_shadow:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/shadow^root:\$(y|[0-9].+)\$.*$1
Ensure that System Accounts Are Lockedxccdf_org.ssgproject.content_rule_no_password_auth_for_systemaccounts mediumCCE-90273-4

Ensure that System Accounts Are Locked

Rule IDxccdf_org.ssgproject.content_rule_no_password_auth_for_systemaccounts
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-no_password_auth_for_systemaccounts:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-90273-4

References:
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistAC-6, CM-6(a)
cis5.4.2.7
pcidss48.2.2, 8.2
Description
Some accounts are not associated with a human user of the system, and exist to perform some administrative functions. An attacker should not be able to log into these accounts.

System accounts are those user accounts with a user ID less than 1000. If any system account other than root, halt, sync, shutdown and nfsnobody has an unlocked password, disable it with the command:
$ sudo usermod -L account
         
Rationale
Disabling authentication for default system accounts makes it more difficult for attackers to make use of them to compromise a system.
OVAL test results details

system accounts with a password defined  oval:ssg-test_no_password_auth_for_systemaccounts:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_no_password_auth_for_systemaccounts:obj:1 of type shadow_object
UsernameFilter
operator
bin
games
ftp
daemon
yggdrasil
yggdrasil-worker
dbus
tss
systemd-oom
polkitd
dhcpcd
sssd
sshd
chrony
systemd-coredump
lp
adm
mail
oval:ssg-filter_no_password_auth_for_systemaccounts_no_passwords_or_locked_accounts:ste:1
Ensure that System Accounts Do Not Run a Shell Upon Loginxccdf_org.ssgproject.content_rule_no_shelllogin_for_systemaccounts mediumCCE-87448-7

Ensure that System Accounts Do Not Run a Shell Upon Login

Rule IDxccdf_org.ssgproject.content_rule_no_shelllogin_for_systemaccounts
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-no_shelllogin_for_systemaccounts:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87448-7

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8
cobit5DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS06.03
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2
ism1491
iso27001-2013A.12.4.1, A.12.4.3, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistAC-6, CM-6(a), CM-6(b), CM-6.1(iv)
nist-csfDE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6
os-srgSRG-OS-000480-GPOS-00227
cis5.4.2.7
pcidss48.2.2, 8.2
Description
Some accounts are not associated with a human user of the system, and exist to perform some administrative functions. Should an attacker be able to log into these accounts, they should not be granted access to a shell.

The login shell for each local account is stored in the last field of each line in /etc/passwd. System accounts are those user accounts with a user ID less than 1000. The user ID is stored in the third field. If any system account other than root has a login shell, disable it with the command:
$ sudo usermod -s /sbin/nologin account
         
Rationale
Ensuring shells are not given to system accounts upon login makes it more difficult for attackers to make use of system accounts.
Warnings
warning  Do not perform the steps in this section on the root account. Doing so might cause the system to become inaccessible.
OVAL test results details

SYS_UID_MIN not defined in /etc/login.defs  oval:ssg-test_sys_uid_min_not_defined:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/login.defs# # Please note that the parameters in this configuration file control the # behavior of the tools from the shadow-utils component. None of these # tools uses the PAM mechanism, and the utilities that use PAM (such as the # passwd command) should therefore be configured elsewhere. Refer to # /etc/pam.d/system-auth for more information. # # # Delay in seconds before being allowed another attempt after a login failure # Note: When PAM is used, some modules may enforce a minimum delay (e.g. # pam_unix(8) enforces a 2s delay) # #FAIL_DELAY 3 # Currently FAILLOG_ENAB is not supported # # Enable display of unknown usernames when login(1) failures are recorded. # #LOG_UNKFAIL_ENAB no # Currently LOG_OK_LOGINS is not supported # Currently LASTLOG_ENAB is not supported # # Limit the highest user ID number for which the lastlog entries should # be updated. # # No LASTLOG_UID_MAX means that there is no user ID limit for writing # lastlog entries. # #LASTLOG_UID_MAX # Currently MAIL_CHECK_ENAB is not supported # Currently OBSCURE_CHECKS_ENAB is not supported # Currently PORTTIME_CHECKS_ENAB is not supported # Currently QUOTAS_ENAB is not supported # Currently SYSLOG_SU_ENAB is not supported # # Enable "syslog" logging of newgrp(1) and sg(1) activity. # #SYSLOG_SG_ENAB yes # Currently CONSOLE is not supported # Currently SULOG_FILE is not supported # Currently MOTD_FILE is not supported # Currently ISSUE_FILE is not supported # Currently TTYTYPE_FILE is not supported # Currently FTMP_FILE is not supported # Currently NOLOGINS_FILE is not supported # Currently SU_NAME is not supported # *REQUIRED* # Directory where mailboxes reside, _or_ name of file, relative to the # home directory. If you _do_ define both, MAIL_DIR takes precedence. # MAIL_DIR /var/spool/mail #MAIL_FILE .mail # # If defined, file which inhibits all the usual chatter during the login # sequence. If a full pathname, then hushed mode will be enabled if the # user's name or shell are found in the file. If not a full pathname, then # hushed mode will be enabled if the file exists in the user's home directory. # #HUSHLOGIN_FILE .hushlogin #HUSHLOGIN_FILE /etc/hushlogins # Currently ENV_TZ is not supported # Currently ENV_HZ is not supported # # The default PATH settings, for superuser and normal users. # # (they are minimal, add the rest in the shell startup files) #ENV_SUPATH PATH=/sbin:/bin:/usr/sbin:/usr/bin #ENV_PATH PATH=/bin:/usr/bin # # Terminal permissions # # TTYGROUP Login tty will be assigned this group ownership. # TTYPERM Login tty will be set to this permission. # # If you have a write(1) program which is "setgid" to a special group # which owns the terminals, define TTYGROUP as the number of such group # and TTYPERM as 0620. Otherwise leave TTYGROUP commented out and # set TTYPERM to either 622 or 600. # #TTYGROUP tty #TTYPERM 0600 # Currently ERASECHAR, KILLCHAR and ULIMIT are not supported # Default initial "umask" value used by login(1) on non-PAM enabled systems. # Default "umask" value for pam_umask(8) on PAM enabled systems. # UMASK is also used by useradd(8) and newusers(8) to set the mode for new # home directories if HOME_MODE is not set. # 022 is the default value, but 027, or even 077, could be considered # for increased privacy. There is no One True Answer here: each sysadmin # must make up their mind. UMASK 022 # HOME_MODE is used by useradd(8) and newusers(8) to set the mode for new # home directories. # If HOME_MODE is not set, the value of UMASK is used to create the mode. HOME_MODE 0700 # Password aging controls: # # PASS_MAX_DAYS Maximum number of days a password may be used. # PASS_MIN_DAYS Minimum number of days allowed between password changes. # PASS_MIN_LEN Minimum acceptable password length. # PASS_WARN_AGE Number of days warning given before a password expires. # PASS_MAX_DAYS 99999 PASS_MIN_DAYS 0 PASS_MIN_LEN 8 PASS_WARN_AGE 7 # Currently SU_WHEEL_ONLY is not supported # Currently CRACKLIB_DICTPATH is not supported # # Min/max values for automatic uid selection in useradd(8) # UID_MIN 1000 UID_MAX 60000 # System accounts SYS_UID_MIN 201

SYS_UID_MAX not defined in /etc/login.defs  oval:ssg-test_sys_uid_max_not_defined:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/login.defs# # Please note that the parameters in this configuration file control the # behavior of the tools from the shadow-utils component. None of these # tools uses the PAM mechanism, and the utilities that use PAM (such as the # passwd command) should therefore be configured elsewhere. Refer to # /etc/pam.d/system-auth for more information. # # # Delay in seconds before being allowed another attempt after a login failure # Note: When PAM is used, some modules may enforce a minimum delay (e.g. # pam_unix(8) enforces a 2s delay) # #FAIL_DELAY 3 # Currently FAILLOG_ENAB is not supported # # Enable display of unknown usernames when login(1) failures are recorded. # #LOG_UNKFAIL_ENAB no # Currently LOG_OK_LOGINS is not supported # Currently LASTLOG_ENAB is not supported # # Limit the highest user ID number for which the lastlog entries should # be updated. # # No LASTLOG_UID_MAX means that there is no user ID limit for writing # lastlog entries. # #LASTLOG_UID_MAX # Currently MAIL_CHECK_ENAB is not supported # Currently OBSCURE_CHECKS_ENAB is not supported # Currently PORTTIME_CHECKS_ENAB is not supported # Currently QUOTAS_ENAB is not supported # Currently SYSLOG_SU_ENAB is not supported # # Enable "syslog" logging of newgrp(1) and sg(1) activity. # #SYSLOG_SG_ENAB yes # Currently CONSOLE is not supported # Currently SULOG_FILE is not supported # Currently MOTD_FILE is not supported # Currently ISSUE_FILE is not supported # Currently TTYTYPE_FILE is not supported # Currently FTMP_FILE is not supported # Currently NOLOGINS_FILE is not supported # Currently SU_NAME is not supported # *REQUIRED* # Directory where mailboxes reside, _or_ name of file, relative to the # home directory. If you _do_ define both, MAIL_DIR takes precedence. # MAIL_DIR /var/spool/mail #MAIL_FILE .mail # # If defined, file which inhibits all the usual chatter during the login # sequence. If a full pathname, then hushed mode will be enabled if the # user's name or shell are found in the file. If not a full pathname, then # hushed mode will be enabled if the file exists in the user's home directory. # #HUSHLOGIN_FILE .hushlogin #HUSHLOGIN_FILE /etc/hushlogins # Currently ENV_TZ is not supported # Currently ENV_HZ is not supported # # The default PATH settings, for superuser and normal users. # # (they are minimal, add the rest in the shell startup files) #ENV_SUPATH PATH=/sbin:/bin:/usr/sbin:/usr/bin #ENV_PATH PATH=/bin:/usr/bin # # Terminal permissions # # TTYGROUP Login tty will be assigned this group ownership. # TTYPERM Login tty will be set to this permission. # # If you have a write(1) program which is "setgid" to a special group # which owns the terminals, define TTYGROUP as the number of such group # and TTYPERM as 0620. Otherwise leave TTYGROUP commented out and # set TTYPERM to either 622 or 600. # #TTYGROUP tty #TTYPERM 0600 # Currently ERASECHAR, KILLCHAR and ULIMIT are not supported # Default initial "umask" value used by login(1) on non-PAM enabled systems. # Default "umask" value for pam_umask(8) on PAM enabled systems. # UMASK is also used by useradd(8) and newusers(8) to set the mode for new # home directories if HOME_MODE is not set. # 022 is the default value, but 027, or even 077, could be considered # for increased privacy. There is no One True Answer here: each sysadmin # must make up their mind. UMASK 022 # HOME_MODE is used by useradd(8) and newusers(8) to set the mode for new # home directories. # If HOME_MODE is not set, the value of UMASK is used to create the mode. HOME_MODE 0700 # Password aging controls: # # PASS_MAX_DAYS Maximum number of days a password may be used. # PASS_MIN_DAYS Minimum number of days allowed between password changes. # PASS_MIN_LEN Minimum acceptable password length. # PASS_WARN_AGE Number of days warning given before a password expires. # PASS_MAX_DAYS 99999 PASS_MIN_DAYS 0 PASS_MIN_LEN 8 PASS_WARN_AGE 7 # Currently SU_WHEEL_ONLY is not supported # Currently CRACKLIB_DICTPATH is not supported # # Min/max values for automatic uid selection in useradd(8) # UID_MIN 1000 UID_MAX 60000 # System accounts SYS_UID_MIN 201 SYS_UID_MAX 999

<0, UID_MIN - 1> system UIDs having shell set  oval:ssg-test_shell_defined_default_uid_range:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/passwdec2-user:x:1000:1000:Cloud User:/home/ec2-user:/bin/bash

SYS_UID_MIN not defined in /etc/login.defs  oval:ssg-test_sys_uid_min_not_defined:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/login.defs# # Please note that the parameters in this configuration file control the # behavior of the tools from the shadow-utils component. None of these # tools uses the PAM mechanism, and the utilities that use PAM (such as the # passwd command) should therefore be configured elsewhere. Refer to # /etc/pam.d/system-auth for more information. # # # Delay in seconds before being allowed another attempt after a login failure # Note: When PAM is used, some modules may enforce a minimum delay (e.g. # pam_unix(8) enforces a 2s delay) # #FAIL_DELAY 3 # Currently FAILLOG_ENAB is not supported # # Enable display of unknown usernames when login(1) failures are recorded. # #LOG_UNKFAIL_ENAB no # Currently LOG_OK_LOGINS is not supported # Currently LASTLOG_ENAB is not supported # # Limit the highest user ID number for which the lastlog entries should # be updated. # # No LASTLOG_UID_MAX means that there is no user ID limit for writing # lastlog entries. # #LASTLOG_UID_MAX # Currently MAIL_CHECK_ENAB is not supported # Currently OBSCURE_CHECKS_ENAB is not supported # Currently PORTTIME_CHECKS_ENAB is not supported # Currently QUOTAS_ENAB is not supported # Currently SYSLOG_SU_ENAB is not supported # # Enable "syslog" logging of newgrp(1) and sg(1) activity. # #SYSLOG_SG_ENAB yes # Currently CONSOLE is not supported # Currently SULOG_FILE is not supported # Currently MOTD_FILE is not supported # Currently ISSUE_FILE is not supported # Currently TTYTYPE_FILE is not supported # Currently FTMP_FILE is not supported # Currently NOLOGINS_FILE is not supported # Currently SU_NAME is not supported # *REQUIRED* # Directory where mailboxes reside, _or_ name of file, relative to the # home directory. If you _do_ define both, MAIL_DIR takes precedence. # MAIL_DIR /var/spool/mail #MAIL_FILE .mail # # If defined, file which inhibits all the usual chatter during the login # sequence. If a full pathname, then hushed mode will be enabled if the # user's name or shell are found in the file. If not a full pathname, then # hushed mode will be enabled if the file exists in the user's home directory. # #HUSHLOGIN_FILE .hushlogin #HUSHLOGIN_FILE /etc/hushlogins # Currently ENV_TZ is not supported # Currently ENV_HZ is not supported # # The default PATH settings, for superuser and normal users. # # (they are minimal, add the rest in the shell startup files) #ENV_SUPATH PATH=/sbin:/bin:/usr/sbin:/usr/bin #ENV_PATH PATH=/bin:/usr/bin # # Terminal permissions # # TTYGROUP Login tty will be assigned this group ownership. # TTYPERM Login tty will be set to this permission. # # If you have a write(1) program which is "setgid" to a special group # which owns the terminals, define TTYGROUP as the number of such group # and TTYPERM as 0620. Otherwise leave TTYGROUP commented out and # set TTYPERM to either 622 or 600. # #TTYGROUP tty #TTYPERM 0600 # Currently ERASECHAR, KILLCHAR and ULIMIT are not supported # Default initial "umask" value used by login(1) on non-PAM enabled systems. # Default "umask" value for pam_umask(8) on PAM enabled systems. # UMASK is also used by useradd(8) and newusers(8) to set the mode for new # home directories if HOME_MODE is not set. # 022 is the default value, but 027, or even 077, could be considered # for increased privacy. There is no One True Answer here: each sysadmin # must make up their mind. UMASK 022 # HOME_MODE is used by useradd(8) and newusers(8) to set the mode for new # home directories. # If HOME_MODE is not set, the value of UMASK is used to create the mode. HOME_MODE 0700 # Password aging controls: # # PASS_MAX_DAYS Maximum number of days a password may be used. # PASS_MIN_DAYS Minimum number of days allowed between password changes. # PASS_MIN_LEN Minimum acceptable password length. # PASS_WARN_AGE Number of days warning given before a password expires. # PASS_MAX_DAYS 99999 PASS_MIN_DAYS 0 PASS_MIN_LEN 8 PASS_WARN_AGE 7 # Currently SU_WHEEL_ONLY is not supported # Currently CRACKLIB_DICTPATH is not supported # # Min/max values for automatic uid selection in useradd(8) # UID_MIN 1000 UID_MAX 60000 # System accounts SYS_UID_MIN 201

SYS_UID_MAX not defined in /etc/login.defs  oval:ssg-test_sys_uid_max_not_defined:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/login.defs# # Please note that the parameters in this configuration file control the # behavior of the tools from the shadow-utils component. None of these # tools uses the PAM mechanism, and the utilities that use PAM (such as the # passwd command) should therefore be configured elsewhere. Refer to # /etc/pam.d/system-auth for more information. # # # Delay in seconds before being allowed another attempt after a login failure # Note: When PAM is used, some modules may enforce a minimum delay (e.g. # pam_unix(8) enforces a 2s delay) # #FAIL_DELAY 3 # Currently FAILLOG_ENAB is not supported # # Enable display of unknown usernames when login(1) failures are recorded. # #LOG_UNKFAIL_ENAB no # Currently LOG_OK_LOGINS is not supported # Currently LASTLOG_ENAB is not supported # # Limit the highest user ID number for which the lastlog entries should # be updated. # # No LASTLOG_UID_MAX means that there is no user ID limit for writing # lastlog entries. # #LASTLOG_UID_MAX # Currently MAIL_CHECK_ENAB is not supported # Currently OBSCURE_CHECKS_ENAB is not supported # Currently PORTTIME_CHECKS_ENAB is not supported # Currently QUOTAS_ENAB is not supported # Currently SYSLOG_SU_ENAB is not supported # # Enable "syslog" logging of newgrp(1) and sg(1) activity. # #SYSLOG_SG_ENAB yes # Currently CONSOLE is not supported # Currently SULOG_FILE is not supported # Currently MOTD_FILE is not supported # Currently ISSUE_FILE is not supported # Currently TTYTYPE_FILE is not supported # Currently FTMP_FILE is not supported # Currently NOLOGINS_FILE is not supported # Currently SU_NAME is not supported # *REQUIRED* # Directory where mailboxes reside, _or_ name of file, relative to the # home directory. If you _do_ define both, MAIL_DIR takes precedence. # MAIL_DIR /var/spool/mail #MAIL_FILE .mail # # If defined, file which inhibits all the usual chatter during the login # sequence. If a full pathname, then hushed mode will be enabled if the # user's name or shell are found in the file. If not a full pathname, then # hushed mode will be enabled if the file exists in the user's home directory. # #HUSHLOGIN_FILE .hushlogin #HUSHLOGIN_FILE /etc/hushlogins # Currently ENV_TZ is not supported # Currently ENV_HZ is not supported # # The default PATH settings, for superuser and normal users. # # (they are minimal, add the rest in the shell startup files) #ENV_SUPATH PATH=/sbin:/bin:/usr/sbin:/usr/bin #ENV_PATH PATH=/bin:/usr/bin # # Terminal permissions # # TTYGROUP Login tty will be assigned this group ownership. # TTYPERM Login tty will be set to this permission. # # If you have a write(1) program which is "setgid" to a special group # which owns the terminals, define TTYGROUP as the number of such group # and TTYPERM as 0620. Otherwise leave TTYGROUP commented out and # set TTYPERM to either 622 or 600. # #TTYGROUP tty #TTYPERM 0600 # Currently ERASECHAR, KILLCHAR and ULIMIT are not supported # Default initial "umask" value used by login(1) on non-PAM enabled systems. # Default "umask" value for pam_umask(8) on PAM enabled systems. # UMASK is also used by useradd(8) and newusers(8) to set the mode for new # home directories if HOME_MODE is not set. # 022 is the default value, but 027, or even 077, could be considered # for increased privacy. There is no One True Answer here: each sysadmin # must make up their mind. UMASK 022 # HOME_MODE is used by useradd(8) and newusers(8) to set the mode for new # home directories. # If HOME_MODE is not set, the value of UMASK is used to create the mode. HOME_MODE 0700 # Password aging controls: # # PASS_MAX_DAYS Maximum number of days a password may be used. # PASS_MIN_DAYS Minimum number of days allowed between password changes. # PASS_MIN_LEN Minimum acceptable password length. # PASS_WARN_AGE Number of days warning given before a password expires. # PASS_MAX_DAYS 99999 PASS_MIN_DAYS 0 PASS_MIN_LEN 8 PASS_WARN_AGE 7 # Currently SU_WHEEL_ONLY is not supported # Currently CRACKLIB_DICTPATH is not supported # # Min/max values for automatic uid selection in useradd(8) # UID_MIN 1000 UID_MAX 60000 # System accounts SYS_UID_MIN 201 SYS_UID_MAX 999

<0, SYS_UID_MIN> system UIDs having shell set  oval:ssg-test_shell_defined_reserved_uid_range:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/passwdec2-user:x:1000:1000:Cloud User:/home/ec2-user:/bin/bash

<SYS_UID_MIN, SYS_UID_MAX> system UIDS having shell set  oval:ssg-test_shell_defined_dynalloc_uid_range:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/passwdec2-user:x:1000:1000:Cloud User:/home/ec2-user:/bin/bash
Enforce Usage of pam_wheel with Group Parameter for su Authenticationxccdf_org.ssgproject.content_rule_use_pam_wheel_group_for_su mediumCCE-87119-4

Enforce Usage of pam_wheel with Group Parameter for su Authentication

Rule IDxccdf_org.ssgproject.content_rule_use_pam_wheel_group_for_su
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-use_pam_wheel_group_for_su:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-87119-4

References:
cis5.2.7
pcidss42.2.6, 2.2
Description
To ensure that only users who are members of the group set in the group option of pam_wheel.so module can run commands with altered privileges through the su command, make sure that the following line exists in the file /etc/pam.d/su:
auth required pam_wheel.so use_uid group=sugroup
         
Rationale
The su program allows to run commands with a substitute user and group ID. It is commonly used to run commands as the root user. Limiting access to such command is considered a good security practice.
Warnings
warning  Note that ensure_pam_wheel_group_empty rule complements this requirement by ensuring the referenced group exists and has no members.

# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

var_pam_wheel_group_for_su='sugroup'


PAM_CONF=/etc/pam.d/su

pamstr=$(grep -P '^auth\s+required\s+pam_wheel\.so\s+(?=[^#]*\buse_uid\b)(?=[^#]*\bgroup=)' ${PAM_CONF})
if [ -z "$pamstr" ]; then
    sed -Ei '/^auth\b.*\brequired\b.*\bpam_wheel\.so/d' ${PAM_CONF} # remove any remaining uncommented pam_wheel.so line
    sed -Ei "/^auth\s+sufficient\s+pam_rootok\.so.*$/a auth             required        pam_wheel.so use_uid group=${var_pam_wheel_group_for_su}" ${PAM_CONF}
else
    group_val=$(echo -n "$pamstr" | grep -Eo '\bgroup=[_a-z][-0-9_a-z]*' | cut -d '=' -f 2)
    if [ -z "${group_val}" ] || [ ${group_val} != ${var_pam_wheel_group_for_su} ]; then
        sed -Ei "s/(^auth\s+required\s+pam_wheel.so\s+[^#]*group=)[_a-z][-0-9_a-z]*/\1${var_pam_wheel_group_for_su}/" ${PAM_CONF}
    fi
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87119-4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - use_pam_wheel_group_for_su
- name: XCCDF Value var_pam_wheel_group_for_su # promote to variable
  set_fact:
    var_pam_wheel_group_for_su: !!str sugroup
  tags:
    - always

- name: Enforce Usage of pam_wheel with Group Parameter for su Authentication - Add
    the group to the /etc/pam.d/su file
  ansible.builtin.lineinfile:
    path: /etc/pam.d/su
    state: present
    regexp: ^[\s]*#[\s]*auth[\s]+required[\s]+pam_wheel\.so[\s]+use_uid group=$
    line: auth             required        pam_wheel.so use_uid group={{ var_pam_wheel_group_for_su
      }}
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-87119-4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - use_pam_wheel_group_for_su
OVAL test results details

check /etc/pam.d/su for correct setting  oval:ssg-test_use_pam_wheel_group_for_su:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_use_pam_wheel_group_for_su:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/pam.d/su^\s*auth\s+required\s+pam_wheel\.so\s+(?=[^#]*\buse_uid\b)[^#]*\bgroup=([_a-z][-0-9_a-z]*)1
Ensure All Groups on the System Have Unique Group IDxccdf_org.ssgproject.content_rule_group_unique_id mediumCCE-86908-1

Ensure All Groups on the System Have Unique Group ID

Rule IDxccdf_org.ssgproject.content_rule_group_unique_id
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-group_unique_id:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-86908-1

References:
os-srgSRG-OS-000104-GPOS-00051
cis7.2.5
pcidss48.2.1, 8.2
Description
Change the group name or delete groups, so each has a unique id.
Rationale
To assure accountability and prevent unauthenticated access, groups must be identified uniquely to prevent potential misuse and compromise of the system.
Warnings
warning  Automatic remediation of this control is not available due to the unique requirements of each system.
OVAL test results details

There should not exist duplicate group ids in /etc/passwd  oval:ssg-test_etc_group_no_duplicate_group_ids:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-variable_count_of_all_group_ids:var:144
Ensure All Groups on the System Have Unique Group Namesxccdf_org.ssgproject.content_rule_group_unique_name mediumCCE-88449-4

Ensure All Groups on the System Have Unique Group Names

Rule IDxccdf_org.ssgproject.content_rule_group_unique_name
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-group_unique_name:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88449-4

References:
cis7.2.7
pcidss48.2.1, 8.2
Description
Change the group name or delete groups, so each has a unique name.
Rationale
To assure accountability and prevent unauthenticated access, groups must be identified uniquely to prevent potential misuse and compromise of the system.
Warnings
warning  Automatic remediation of this control is not available due to the unique requirements of each system.
OVAL test results details

There should not exist duplicate group names in /etc/passwd  oval:ssg-test_etc_group_no_duplicate_group_names:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-variable_count_of_all_group_names:var:144
Ensure that Root's Path Does Not Include World or Group-Writable Directoriesxccdf_org.ssgproject.content_rule_accounts_root_path_dirs_no_write mediumCCE-88150-8

Ensure that Root's Path Does Not Include World or Group-Writable Directories

Rule IDxccdf_org.ssgproject.content_rule_accounts_root_path_dirs_no_write
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_root_path_dirs_no_write:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-88150-8

References:
cis-csc11, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05
isa-62443-20094.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-6(a), CM-6(a)
nist-csfPR.IP-1
cis5.4.2.5
Description
For each element in root's path, run:
# ls -ld DIR
         
and ensure that write permissions are disabled for group and other.
Rationale
Such entries increase the risk that root could execute code provided by unprivileged users, and potentially malicious code.
OVAL test results details

Check if there aren't directories in root's path having write permission set for group or other  oval:ssg-test_accounts_root_path_dirs_no_group_other_write:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_root_path_dirs_no_group_other_write:obj:1 of type file_object
PathFilenameFilterFilter
/sbin
/bin
/usr/sbin
/usr/bin
no valueoval:ssg-state_accounts_root_path_dirs_wrong_perms:ste:1oval:ssg-state_accounts_root_path_dirs_symlink:ste:1
Ensure that Root's Path Does Not Include Relative Paths or Null Directoriesxccdf_org.ssgproject.content_rule_root_path_no_dot unknownCCE-88793-5

Ensure that Root's Path Does Not Include Relative Paths or Null Directories

Rule IDxccdf_org.ssgproject.content_rule_root_path_no_dot
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-root_path_no_dot:def:1
Time2025-07-22T08:31:41+00:00
Severityunknown
Identifiers:

CCE-88793-5

References:
cis-csc11, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05
isa-62443-20094.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-6(a), CM-6(a)
nist-csfPR.IP-1
cis5.4.2.5
Description
Ensure that none of the directories in root's path is equal to a single . character, or that it contains any instances that lead to relative path traversal, such as .. or beginning a path without the slash (/) character. Also ensure that there are no "empty" elements in the path, such as in these examples:
PATH=:/bin
PATH=/bin:
PATH=/bin::/sbin
These empty elements have the same effect as a single . character.
Rationale
Including these entries increases the risk that root could execute code from an untrusted location.
OVAL test results details

environment variable PATH starts with : or .  oval:ssg-test_env_var_begins:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPidNameValue
false8219PATH/sbin:/bin:/usr/sbin:/usr/bin

environment variable PATH doesn't contain : twice in a row  oval:ssg-test_env_var_contains_doublecolon:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPidNameValue
false8219PATH/sbin:/bin:/usr/sbin:/usr/bin

environment variable PATH doesn't contain . twice in a row  oval:ssg-test_env_var_contains_doubleperiod:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPidNameValue
false8219PATH/sbin:/bin:/usr/sbin:/usr/bin

environment variable PATH ends with : or .  oval:ssg-test_env_var_ends:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPidNameValue
false8219PATH/sbin:/bin:/usr/sbin:/usr/bin

environment variable PATH starts with an absolute path /  oval:ssg-test_env_var_begins_slash:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPidNameValue
false8219PATH/sbin:/bin:/usr/sbin:/usr/bin

environment variable PATH contains relative paths  oval:ssg-test_env_var_contains_relative_path:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPidNameValue
false8219PATH/sbin:/bin:/usr/sbin:/usr/bin
Ensure the Default Bash Umask is Set Correctlyxccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc mediumCCE-88580-6

Ensure the Default Bash Umask is Set Correctly

Rule IDxccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_umask_etc_bashrc:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-88580-6

References:
cis-csc18
cobit5APO13.01, BAI03.01, BAI03.02, BAI03.03
isa-62443-20094.3.4.3.3
iso27001-2013A.14.1.1, A.14.2.1, A.14.2.5, A.6.1.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistAC-6(1), CM-6(a)
nist-csfPR.IP-2
os-srgSRG-OS-000480-GPOS-00228, SRG-OS-000480-GPOS-00227
anssiR36
cis5.4.3.3
Description
To ensure the default umask for users of the Bash shell is set properly, add or correct the umask setting in /etc/bashrc to read as follows:
umask 027
         
Rationale
The umask value influences the permissions assigned to files when they are created. A misconfigured umask value could result in files with excessive permissions that can be read or written to by unauthorized users.

# Remediation is applicable only in certain platforms
if rpm --quiet -q bash; then

var_accounts_user_umask='027'






grep -q "^[^#]*\bumask" /etc/bashrc && \
  sed -i -E -e "s/^([^#]*\bumask)[[:space:]]+[[:digit:]]+/\1 $var_accounts_user_umask/g" /etc/bashrc
if ! [ $? -eq 0 ]; then
    echo "umask $var_accounts_user_umask" >> /etc/bashrc
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88580-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_bashrc
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_user_umask # promote to variable
  set_fact:
    var_accounts_user_umask: !!str 027
  tags:
    - always

- name: Check if umask in /etc/bashrc is already set
  ansible.builtin.lineinfile:
    path: /etc/bashrc
    regexp: ^[^#]*\bumask\s+\d+$
    state: absent
  check_mode: true
  changed_when: false
  register: umask_replace
  when: '"bash" in ansible_facts.packages'
  tags:
  - CCE-88580-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_bashrc
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Replace user umask in /etc/bashrc
  ansible.builtin.replace:
    path: /etc/bashrc
    regexp: ^([^#]*\b)umask\s+\d+$
    replace: \g<1>umask {{ var_accounts_user_umask }}
  when:
  - '"bash" in ansible_facts.packages'
  - umask_replace.found > 0
  tags:
  - CCE-88580-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_bashrc
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure the Default umask is Appended Correctly
  ansible.builtin.lineinfile:
    create: true
    path: /etc/bashrc
    line: umask {{ var_accounts_user_umask }}
  when:
  - '"bash" in ansible_facts.packages'
  - umask_replace.found == 0
  tags:
  - CCE-88580-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_bashrc
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

Verify the existence of var_accounts_user_umask_as_number variable  oval:ssg-test_existence_of_var_accounts_user_umask_as_number_variable:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
not evaluatedoval:ssg-var_accounts_user_umask_umask_as_number:var:123

Test the retrieved /etc/bashrc umask value(s) match the var_accounts_user_umask requirement  oval:ssg-tst_accounts_umask_etc_bashrc:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-var_etc_bashrc_umask_as_number:var:118
Ensure the Default Umask is Set Correctly in /etc/profilexccdf_org.ssgproject.content_rule_accounts_umask_etc_profile mediumCCE-87651-6

Ensure the Default Umask is Set Correctly in /etc/profile

Rule IDxccdf_org.ssgproject.content_rule_accounts_umask_etc_profile
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_umask_etc_profile:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-87651-6

References:
cis-csc18
cobit5APO13.01, BAI03.01, BAI03.02, BAI03.03
isa-62443-20094.3.4.3.3
iso27001-2013A.14.1.1, A.14.2.1, A.14.2.5, A.6.1.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistAC-6(1), CM-6(a)
nist-csfPR.IP-2
os-srgSRG-OS-000480-GPOS-00228, SRG-OS-000480-GPOS-00227
anssiR36
cis5.4.3.3
Description
To ensure the default umask controlled by /etc/profile is set properly, add or correct the umask setting in /etc/profile to read as follows:
umask 027
         
Note that /etc/profile also reads scrips within /etc/profile.d directory. These scripts are also valid files to set umask value. Therefore, they should also be considered during the check and properly remediated, if necessary.
Rationale
The umask value influences the permissions assigned to files when they are created. A misconfigured umask value could result in files with excessive permissions that can be read or written to by unauthorized users.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict

var_accounts_user_umask='027'


readarray -t profile_files < <(find /etc/profile.d/ -type f -name '*.sh' -or -name 'sh.local')

for file in "${profile_files[@]}" /etc/profile; do
  grep -qE '^[^#]*umask' "$file" && sed -i -E "s/^(\s*umask\s*)[0-7]+/\1$var_accounts_user_umask/g" "$file"
done

if ! grep -qrE '^[^#]*umask' /etc/profile*; then
  echo "umask $var_accounts_user_umask" >> /etc/profile
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_accounts_user_umask # promote to variable
  set_fact:
    var_accounts_user_umask: !!str 027
  tags:
    - always

- name: Ensure the Default Umask is Set Correctly in /etc/profile - Locate Profile
    Configuration Files Where umask Is Defined
  ansible.builtin.find:
    paths:
    - /etc/profile.d
    patterns:
    - sh.local
    - '*.sh'
    contains: ^[\s]*umask\s+\d+
  register: result_profile_d_files
  tags:
  - CCE-87651-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure the Default Umask is Set Correctly in /etc/profile - Replace Existing
    umask Value in Files From /etc/profile.d
  ansible.builtin.replace:
    path: '{{ item.path }}'
    regexp: ^(\s*)umask\s+\d+
    replace: \1umask {{ var_accounts_user_umask }}
  loop: '{{ result_profile_d_files.files }}'
  register: result_umask_replaced_profile_d
  when: result_profile_d_files.matched
  tags:
  - CCE-87651-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure the Default Umask is Set Correctly in /etc/profile - Ensure umask Is
    Set in /etc/profile if Not Already Set Elsewhere
  ansible.builtin.lineinfile:
    create: true
    mode: 420
    path: /etc/profile
    line: umask {{ var_accounts_user_umask }}
  when: not result_profile_d_files.matched
  tags:
  - CCE-87651-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure the Default Umask is Set Correctly in /etc/profile - Ensure umask Value
    For All Existing umask Definition in /etc/profile
  ansible.builtin.replace:
    path: /etc/profile
    regexp: ^(\s*)umask\s+\d+
    replace: \1umask {{ var_accounts_user_umask }}
  register: result_umask_replaced_profile
  tags:
  - CCE-87651-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

Verify the existence of var_accounts_user_umask_as_number variable  oval:ssg-test_existence_of_var_accounts_user_umask_as_number_variable:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
not evaluatedoval:ssg-var_accounts_user_umask_umask_as_number:var:123

umask value(s) from profile configuration files match the requirement  oval:ssg-tst_accounts_umask_etc_profile:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_accounts_umask_etc_profile:obj:1 of type variable_object
Var ref
oval:ssg-var_etc_profile_umask_as_number:var:1
Set Interactive Session Timeoutxccdf_org.ssgproject.content_rule_accounts_tmout mediumCCE-88163-1

Set Interactive Session Timeout

Rule IDxccdf_org.ssgproject.content_rule_accounts_tmout
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_tmout:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-88163-1

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.11
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nerc-cipCIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistAC-12, SC-10, AC-2(5), CM-6(a)
nist-csfPR.AC-7
os-srgSRG-OS-000163-GPOS-00072, SRG-OS-000029-GPOS-00010
anssiR32
cis5.4.3.2
pcidss48.6.1, 8.6
Description
Setting the TMOUT option in /etc/profile ensures that all user sessions will terminate based on inactivity. The value of TMOUT should be exported and read only. The TMOUT setting in a file loaded by /etc/profile, e.g. /etc/profile.d/tmout.sh should read as follows:
typeset -xr TMOUT=900
        
or
declare -xr TMOUT=900
        
Using the typeset keyword is preferred for wider compatibility with ksh and other shells.
Rationale
Terminating an idle session within a short time period reduces the window of opportunity for unauthorized personnel to take control of a management session enabled on the console or console port that has been left unattended.

# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

var_accounts_tmout='900'


# if 0, no occurence of tmout found, if 1, occurence found
tmout_found=0


for f in /etc/profile /etc/profile.d/*.sh; do

    if grep --silent '^[^#].*TMOUT' $f; then
        sed -i -E "s/^(.*)TMOUT\s*=\s*(\w|\$)*(.*)$/typeset -xr TMOUT=$var_accounts_tmout\3/g" $f
        tmout_found=1
    fi
done

if [ $tmout_found -eq 0 ]; then
        echo -e "\n# Set TMOUT to $var_accounts_tmout per security requirements" >> /etc/profile.d/tmout.sh
        echo "typeset -xr TMOUT=$var_accounts_tmout" >> /etc/profile.d/tmout.sh
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88163-1
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_tmout # promote to variable
  set_fact:
    var_accounts_tmout: !!str 900
  tags:
    - always

- name: Correct any occurrence of TMOUT in /etc/profile
  replace:
    path: /etc/profile
    regexp: ^[^#].*TMOUT=.*
    replace: typeset -xr TMOUT={{ var_accounts_tmout }}
  register: profile_replaced
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88163-1
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Interactive Session Timeout
  lineinfile:
    path: /etc/profile.d/tmout.sh
    create: true
    regexp: TMOUT=
    line: typeset -xr TMOUT={{ var_accounts_tmout }}
    state: present
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88163-1
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

TMOUT in /etc/profile  oval:ssg-test_etc_profile_tmout:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_etc_profile_tmout:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/profile^[\s]*(?:typeset|declare)[\s]+-xr[\s]+TMOUT=([\w$]+).*$1

TMOUT in /etc/profile.d/*.sh  oval:ssg-test_etc_profiled_tmout:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_etc_profiled_tmout:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/profile.d^.*\.sh$^[\s]*(?:typeset|declare)[\s]+-xr[\s]+TMOUT=([\w$]+).*$1

Check that at least one TMOUT is defined  oval:ssg-test_accounts_tmout_defined:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_tmout_defined:obj:1 of type variable_object
Var ref
oval:ssg-variable_count_of_tmout_instances:var:1
User Initialization Files Must Be Group-Owned By The Primary Groupxccdf_org.ssgproject.content_rule_accounts_user_dot_group_ownership mediumCCE-89225-7

User Initialization Files Must Be Group-Owned By The Primary Group

Rule IDxccdf_org.ssgproject.content_rule_accounts_user_dot_group_ownership
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_user_dot_group_ownership:def:1
Time2025-07-22T08:31:38+00:00
Severitymedium
Identifiers:

CCE-89225-7

References:
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.2.9
Description
Change the group owner of interactive users files to the group found in
/etc/passwd
for the user. To change the group owner of a local interactive user home directory, use the following command:
$ sudo chgrp USER_GROUP /home/USER/.INIT_FILE
        
This rule ensures every initialization file related to an interactive user is group-owned by an interactive user.
Rationale
Local initialization files for interactive users are used to configure the user's shell environment upon logon. Malicious modification of these files could compromise accounts upon logon.
Warnings
warning  Due to OVAL limitation, this rule can report a false negative in a specific situation where two interactive users swap the group-ownership of their respective initialization files.
OVAL test results details

All user initialization files are group-owned by a local interactive user  oval:ssg-test_accounts_user_dot_group_ownership:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
true/home/ec2-user/.bash_logoutregular1000100018rw-r--r-- 
true/home/ec2-user/.bash_profileregular10001000144rw-r--r-- 
true/home/ec2-user/.bashrcregular10001000522rw-r--r-- 
User Initialization Files Must Not Run World-Writable Programsxccdf_org.ssgproject.content_rule_accounts_user_dot_no_world_writable_programs mediumCCE-90449-0

User Initialization Files Must Not Run World-Writable Programs

Rule IDxccdf_org.ssgproject.content_rule_accounts_user_dot_no_world_writable_programs
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_user_dot_no_world_writable_programs:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-90449-0

References:
os-srgSRG-OS-000480-GPOS-00227
cis7.2.9
Description
Set the mode on files being executed by the user initialization files with the following command:
$ sudo chmod o-w FILE
        
Rationale
If user start-up files execute world-writable programs, especially in unprotected directories, they could be maliciously modified to destroy user files or otherwise compromise the system at the user level. If the system is compromised at the user level, it is easier to elevate privileges to eventually compromise the system at the root and network level.
OVAL test results details

Init files do not execute world-writable programs  oval:ssg-test_accounts_user_dot_no_world_writable_programs:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_accounts_user_dot_no_world_writable_programs_init_files:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
^\.[\w\- ]+$
/home/ec2-user
Referenced variable has no values (oval:ssg-var_world_writable_programs_regex:var:1).
1
User Initialization Files Must Be Owned By the Primary Userxccdf_org.ssgproject.content_rule_accounts_user_dot_user_ownership mediumCCE-86981-8

User Initialization Files Must Be Owned By the Primary User

Rule IDxccdf_org.ssgproject.content_rule_accounts_user_dot_user_ownership
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_user_dot_user_ownership:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-86981-8

References:
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.2.9
Description
Set the owner of the user initialization files for interactive users to the primary owner with the following command:
$ sudo chown USER /home/USER/.*
This rule ensures every initialization file related to an interactive user is owned by an interactive user.
Rationale
Local initialization files are used to configure the user's shell environment upon logon. Malicious modification of these files could compromise accounts upon logon.
Warnings
warning  Due to OVAL limitation, this rule can report a false negative in a specific situation where two interactive users swap the ownership of their respective initialization files.
OVAL test results details

All user initialization files are owned by a local interactive user  oval:ssg-test_accounts_user_dot_user_ownership:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
true/home/ec2-user/.bash_logoutregular1000100018rw-r--r-- 
true/home/ec2-user/.bash_profileregular10001000144rw-r--r-- 
true/home/ec2-user/.bashrcregular10001000522rw-r--r-- 
All Interactive Users Home Directories Must Existxccdf_org.ssgproject.content_rule_accounts_user_interactive_home_directory_exists mediumCCE-86659-0

All Interactive Users Home Directories Must Exist

Rule IDxccdf_org.ssgproject.content_rule_accounts_user_interactive_home_directory_exists
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-accounts_user_interactive_home_directory_exists:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-86659-0

References:
os-srgSRG-OS-000480-GPOS-00227
cis7.2.8
Description
Create home directories to all local interactive users that currently do not have a home directory assigned. Use the following commands to create the user home directory assigned in /etc/passwd:
$ sudo mkdir /home/USER
        
Rationale
If a local interactive user has a home directory defined that does not exist, the user may be given access to the / directory as the current working directory upon logon. This could create a Denial of Service because the user would not be able to access their logon configuration files, and it may give them visibility to system files they normally would not be able to access.
OVAL test results details

Check the existence of interactive users.  oval:ssg-test_accounts_user_interactive_home_directory_exists:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-var_accounts_user_interactive_home_directory_exists_dirs_count_fs:var:11

Check the existence of interactive users.  oval:ssg-test_accounts_user_interactive_home_directory_exists_users:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
not evaluatedoval:ssg-var_accounts_user_interactive_home_directory_exists_dirs_count:var:11
All Interactive User Home Directories Must Be Owned By The Primary Userxccdf_org.ssgproject.content_rule_file_ownership_home_directories mediumCCE-88344-7

All Interactive User Home Directories Must Be Owned By The Primary User

Rule IDxccdf_org.ssgproject.content_rule_file_ownership_home_directories
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_ownership_home_directories:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-88344-7

References:
os-srgSRG-OS-000480-GPOS-00227
cis7.2.8
Description
Change the owner of interactive users home directories to that correct owner. To change the owner of a interactive users home directory, use the following command:
$ sudo chown USER /home/USER
        
This rule ensures every home directory related to an interactive user is owned by an interactive user. It also ensures that interactive users are owners of one and only one home directory.
Rationale
If a local interactive user does not own their home directory, unauthorized users could access or modify the user's files, and the users may not be able to access their own files.
Warnings
warning  Due to OVAL limitation, this rule can report a false negative in a specific situation where two interactive users swap the ownership of their respective home directories.
OVAL test results details

All home directories are owned by a local interactive user  oval:ssg-test_file_ownership_home_directories:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
true/home/ec2-user/directory1000100090rwx------ 

It should not exist duplicated owners of home dirs  oval:ssg-test_file_ownership_home_directories_duplicated:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-var_file_ownership_home_directories_uids_count:var:11
Ensure All User Initialization Files Have Mode 0740 Or Less Permissivexccdf_org.ssgproject.content_rule_file_permission_user_init_files mediumCCE-87771-2

Ensure All User Initialization Files Have Mode 0740 Or Less Permissive

Rule IDxccdf_org.ssgproject.content_rule_file_permission_user_init_files
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permission_user_init_files:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-87771-2

References:
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.2.9
Description
Set the mode of the user initialization files to 0740 with the following command:
$ sudo chmod 0740 /home/USER/.INIT_FILE
        
Rationale
Local initialization files are used to configure the user's shell environment upon logon. Malicious modification of these files could compromise accounts upon logon.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict

var_user_initialization_files_regex='^\.[\w\- ]+$'


readarray -t interactive_users < <(awk -F: '$3>=1000   {print $1}' /etc/passwd)
readarray -t interactive_users_home < <(awk -F: '$3>=1000   {print $6}' /etc/passwd)
readarray -t interactive_users_shell < <(awk -F: '$3>=1000   {print $7}' /etc/passwd)

USERS_IGNORED_REGEX='nobody|nfsnobody'

for (( i=0; i<"${#interactive_users[@]}"; i++ )); do
    if ! grep -qP "$USERS_IGNORED_REGEX" <<< "${interactive_users[$i]}" && \
        [ "${interactive_users_shell[$i]}" != "/sbin/nologin" ]; then
        
        readarray -t init_files < <(find "${interactive_users_home[$i]}" -maxdepth 1 \
            -exec basename {} \; | grep -P "$var_user_initialization_files_regex")
        for file in "${init_files[@]}"; do
            chmod u-s,g-wxs,o= "${interactive_users_home[$i]}/$file"
        done
    fi
done

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_user_initialization_files_regex # promote to variable
  set_fact:
    var_user_initialization_files_regex: !!str ^\.[\w\- ]+$
  tags:
    - always

- name: Ensure All User Initialization Files Have Mode 0740 Or Less Permissive - Gather
    User Info
  ansible.builtin.getent:
    database: passwd
  tags:
  - CCE-87771-2
  - file_permission_user_init_files
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All User Initialization Files Have Mode 0740 Or Less Permissive - Find
    Init Files
  ansible.builtin.find:
    paths: '{{ item.value[4] }}'
    pattern: '{{ var_user_initialization_files_regex }}'
    hidden: true
    use_regex: true
  with_dict: '{{ ansible_facts.getent_passwd }}'
  when:
  - item.value[4] != "/sbin/nologin"
  - item.key not in ["nobody", "nfsnobody"]
  - item.value[1] | int >= 1000
  register: found_init_files
  tags:
  - CCE-87771-2
  - file_permission_user_init_files
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All User Initialization Files Have Mode 0740 Or Less Permissive - Fix
    Init Files Permissions
  ansible.builtin.file:
    path: '{{ item.1.path }}'
    mode: u-s,g-wxs,o=
  loop: '{{ q(''ansible.builtin.subelements'', found_init_files.results, ''files'',
    {''skip_missing'': True}) }}'
  tags:
  - CCE-87771-2
  - file_permission_user_init_files
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

Init files have mode 0740 or less permissive  oval:ssg-test_file_permission_user_init_files:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
false/home/ec2-user/.bash_logoutregular1000100018rw-r--r-- 
false/home/ec2-user/.bash_profileregular10001000144rw-r--r-- 
false/home/ec2-user/.bashrcregular10001000522rw-r--r-- 
All Interactive User Home Directories Must Have mode 0750 Or Less Permissivexccdf_org.ssgproject.content_rule_file_permissions_home_directories mediumCCE-86605-3

All Interactive User Home Directories Must Have mode 0750 Or Less Permissive

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_home_directories
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_home_directories:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-86605-3

References:
os-srgSRG-OS-000480-GPOS-00227
cis7.2.8
Description
Change the mode of interactive users home directories to 0750. To change the mode of interactive users home directory, use the following command:
$ sudo chmod 0750 /home/USER
        
Rationale
Excessive permissions on local interactive user home directories may allow unauthorized access to user files by other users.
OVAL test results details

All home directories have proper permissions  oval:ssg-test_file_permissions_home_directories:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
true/home/ec2-user/directory1000100090rwx------ 
Verify /boot/grub2/grub.cfg Group Ownershipxccdf_org.ssgproject.content_rule_file_groupowner_grub2_cfg mediumCCE-88691-1

Verify /boot/grub2/grub.cfg Group Ownership

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_grub2_cfg
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_grub2_cfg:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-88691-1

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
os-srgSRG-OS-000480-GPOS-00227
anssiR29
cis1.4.2
pcidss42.2.6, 2.2
Description
The file /boot/grub2/grub.cfg should be group-owned by the root group to prevent destruction or modification of the file. To properly set the group owner of /boot/grub2/grub.cfg, run the command:
$ sudo chgrp root /boot/grub2/grub.cfg
Rationale
The root group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway.
OVAL test results details

Testing group ownership of /boot/grub2/grub.cfg  oval:ssg-test_file_groupowner_grub2_cfg_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_grub2_cfg_0:obj:1 of type file_object
FilepathFilterFilter
/boot/grub2/grub.cfgoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_grub2_cfg_0_0:ste:1
Verify /boot/grub2/user.cfg Group Ownershipxccdf_org.ssgproject.content_rule_file_groupowner_user_cfg mediumCCE-86797-8

Verify /boot/grub2/user.cfg Group Ownership

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_user_cfg
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_user_cfg:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-86797-8

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
os-srgSRG-OS-000480-GPOS-00227
anssiR29
cis1.4.2
pcidss42.2.6, 2.2
Description
The file /boot/grub2/user.cfg should be group-owned by the root group to prevent reading or modification of the file. To properly set the group owner of /boot/grub2/user.cfg, run the command:
$ sudo chgrp root /boot/grub2/user.cfg
Rationale
The root group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway. Non-root users who read the boot parameters may be able to identify weaknesses in security upon boot and be able to exploit them.
OVAL test results details

Testing group ownership of /boot/grub2/user.cfg  oval:ssg-test_file_groupowner_user_cfg_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_user_cfg_0:obj:1 of type file_object
FilepathFilterFilter
/boot/grub2/user.cfgoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_user_cfg_0_0:ste:1
Verify /boot/grub2/grub.cfg User Ownershipxccdf_org.ssgproject.content_rule_file_owner_grub2_cfg mediumCCE-89438-6

Verify /boot/grub2/grub.cfg User Ownership

Rule IDxccdf_org.ssgproject.content_rule_file_owner_grub2_cfg
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_grub2_cfg:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-89438-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
os-srgSRG-OS-000480-GPOS-00227
anssiR29
cis1.4.2
pcidss42.2.6, 2.2
Description
The file /boot/grub2/grub.cfg should be owned by the root user to prevent destruction or modification of the file. To properly set the owner of /boot/grub2/grub.cfg, run the command:
$ sudo chown root /boot/grub2/grub.cfg 
Rationale
Only root should be able to modify important boot parameters.
OVAL test results details

Testing user ownership of /boot/grub2/grub.cfg  oval:ssg-test_file_owner_grub2_cfg_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_grub2_cfg_0:obj:1 of type file_object
FilepathFilterFilter
/boot/grub2/grub.cfgoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_grub2_cfg_0_0:ste:1
Verify /boot/grub2/user.cfg User Ownershipxccdf_org.ssgproject.content_rule_file_owner_user_cfg mediumCCE-90573-7

Verify /boot/grub2/user.cfg User Ownership

Rule IDxccdf_org.ssgproject.content_rule_file_owner_user_cfg
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_user_cfg:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-90573-7

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
anssiR29
cis1.4.2
pcidss42.2.6, 2.2
Description
The file /boot/grub2/user.cfg should be owned by the root user to prevent reading or modification of the file. To properly set the owner of /boot/grub2/user.cfg, run the command:
$ sudo chown root /boot/grub2/user.cfg 
Rationale
Only root should be able to modify important boot parameters. Also, non-root users who read the boot parameters may be able to identify weaknesses in security upon boot and be able to exploit them.
OVAL test results details

Testing user ownership of /boot/grub2/user.cfg  oval:ssg-test_file_owner_user_cfg_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_user_cfg_0:obj:1 of type file_object
FilepathFilterFilter
/boot/grub2/user.cfgoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_user_cfg_0_0:ste:1
Verify /boot/grub2/grub.cfg Permissionsxccdf_org.ssgproject.content_rule_file_permissions_grub2_cfg mediumCCE-89290-1

Verify /boot/grub2/grub.cfg Permissions

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_grub2_cfg
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_grub2_cfg:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-89290-1

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR29
cis1.4.2
pcidss42.2.6, 2.2
Description
File permissions for /boot/grub2/grub.cfg should be set to 600. To properly set the permissions of /boot/grub2/grub.cfg, run the command:
$ sudo chmod 600 /boot/grub2/grub.cfg
Rationale
Proper permissions ensure that only the root user can modify important boot parameters.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if ( rpm --quiet -q grub2-common && rpm --quiet -q kernel ) && { ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} ); }; then

chmod u-xs,g-xwrs,o-xwrt /boot/grub2/grub.cfg

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89290-1
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Test for existence /boot/grub2/grub.cfg
  stat:
    path: /boot/grub2/grub.cfg
  register: file_exists
  when:
  - ( "grub2-common" in ansible_facts.packages and "kernel" in ansible_facts.packages
    )
  - not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    )
  tags:
  - CCE-89290-1
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xwrs,o-xwrt on /boot/grub2/grub.cfg
  file:
    path: /boot/grub2/grub.cfg
    mode: u-xs,g-xwrs,o-xwrt
  when:
  - ( "grub2-common" in ansible_facts.packages and "kernel" in ansible_facts.packages
    )
  - not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    )
  - file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-89290-1
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Testing mode of /boot/grub2/grub.cfg  oval:ssg-test_file_permissions_grub2_cfg_0:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/boot/grub2/grub.cfgregular007113rw-r--r-- 
Verify /boot/grub2/user.cfg Permissionsxccdf_org.ssgproject.content_rule_file_permissions_user_cfg mediumCCE-90099-3

Verify /boot/grub2/user.cfg Permissions

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_user_cfg
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_user_cfg:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-90099-3

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR29
cis1.4.2
pcidss42.2.6, 2.2
Description
File permissions for /boot/grub2/user.cfg should be set to 600. To properly set the permissions of /boot/grub2/user.cfg, run the command:
$ sudo chmod 600 /boot/grub2/user.cfg
Rationale
Proper permissions ensure that only the root user can read or modify important boot parameters.
OVAL test results details

Testing mode of /boot/grub2/user.cfg  oval:ssg-test_file_permissions_user_cfg_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_user_cfg_0:obj:1 of type file_object
FilepathFilterFilter
/boot/grub2/user.cfgoval:ssg-exclude_symlinks__user_cfg:ste:1oval:ssg-state_file_permissions_user_cfg_0_mode_0600or_stricter_:ste:1
Set Boot Loader Password in grub2xccdf_org.ssgproject.content_rule_grub2_password highCCE-87614-4

Set Boot Loader Password in grub2

Rule IDxccdf_org.ssgproject.content_rule_grub2_password
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-grub2_password:def:1
Time2025-07-22T08:31:41+00:00
Severityhigh
Identifiers:

CCE-87614-4

References:
cis-csc1, 11, 12, 14, 15, 16, 18, 3, 5
cobit5DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10
cui3.4.5
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7
iso27001-2013A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistCM-6(a)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3
osppFIA_UAU.1
os-srgSRG-OS-000080-GPOS-00048
anssiR5
cis1.4.1
Description
The grub2 boot loader should have a superuser account and password protection enabled to protect boot-time settings.

Since plaintext passwords are a security risk, generate a hash for the password by running the following command:
# grub2-setpassword
When prompted, enter the password that was selected.

Rationale
Password protection on the boot loader configuration ensures users with physical access cannot trivially alter important bootloader settings. These include which kernel to use, and whether to enter single-user mode.
Warnings
warning  To prevent hard-coded passwords, automatic remediation of this control is not available. Remediation must be automated as a component of machine provisioning, or followed manually as outlined above. Also, do NOT manually add the superuser account and password to the grub.cfg file as the grub2-mkconfig command overwrites this file.
OVAL test results details

make sure a password is defined in /boot/grub2/user.cfg  oval:ssg-test_grub2_password_usercfg:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_grub2_password_usercfg:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/boot/grub2/user.cfg^[\s]*GRUB2_PASSWORD=grub\.pbkdf2\.sha512.*$1

make sure a password is defined in /boot/grub2/grub.cfg  oval:ssg-test_grub2_password_grubcfg:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_grub2_password_grubcfg:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/boot/grub2/grub.cfg^[\s]*password_pbkdf2[\s]+.*[\s]+grub\.pbkdf2\.sha512.*$1

superuser is defined in /boot/grub2/grub.cfg files.  oval:ssg-test_bootloader_superuser:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/boot/grub2/grub.cfg set superusers="root"
Ensure Log Files Are Owned By Appropriate Groupxccdf_org.ssgproject.content_rule_rsyslog_files_groupownership mediumCCE-90074-6

Ensure Log Files Are Owned By Appropriate Group

Rule IDxccdf_org.ssgproject.content_rule_rsyslog_files_groupownership
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-rsyslog_files_groupownership:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-90074-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
ism0988, 1405
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-10.5.1, Req-10.5.2
anssiR71
cis6.2.4.1
pcidss410.3.2, 10.3
Description
The group-owner of all log files written by rsyslog should be root. These log files are determined by the second part of each Rule line in /etc/rsyslog.conf and typically all appear in /var/log. For each log file LOGFILE referenced in /etc/rsyslog.conf, run the following command to inspect the file's group owner:
$ ls -l LOGFILE
        
If the owner is not root, run the following command to correct this:
$ sudo chgrp root LOGFILE
        
Rationale
The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.
OVAL test results details

System log files have appropriate groupowner set  oval:ssg-test_rsyslog_files_groupownership:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
true/var/log/maillogregular000rw------- 
true/var/log/cloud-init.logregular00259538rw-r----- 
true/var/log/secureregular0025638rw------- 
true/var/log/cronregular003550rw------- 
true/var/log/spoolerregular000rw------- 
true/var/log/messagesregular00337290rw------- 
Ensure Log Files Are Owned By Appropriate Userxccdf_org.ssgproject.content_rule_rsyslog_files_ownership mediumCCE-88780-2

Ensure Log Files Are Owned By Appropriate User

Rule IDxccdf_org.ssgproject.content_rule_rsyslog_files_ownership
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-rsyslog_files_ownership:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-88780-2

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
ism0988, 1405
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-10.5.1, Req-10.5.2
anssiR71
cis6.2.4.1
pcidss410.3.2, 10.3
Description
The owner of all log files written by rsyslog should be root. These log files are determined by the second part of each Rule line in /etc/rsyslog.conf and typically all appear in /var/log. For each log file LOGFILE referenced in /etc/rsyslog.conf, run the following command to inspect the file's owner:
$ ls -l LOGFILE
        
If the owner is not root, run the following command to correct this:
$ sudo chown root LOGFILE
        
Rationale
The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.
OVAL test results details

System log files have appropriate owner set  oval:ssg-test_rsyslog_files_ownership:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
true/var/log/maillogregular000rw------- 
true/var/log/cloud-init.logregular00259538rw-r----- 
true/var/log/secureregular0025638rw------- 
true/var/log/cronregular003550rw------- 
true/var/log/spoolerregular000rw------- 
true/var/log/messagesregular00337290rw------- 
Ensure System Log Files Have Correct Permissionsxccdf_org.ssgproject.content_rule_rsyslog_files_permissions mediumCCE-89058-2

Ensure System Log Files Have Correct Permissions

Rule IDxccdf_org.ssgproject.content_rule_rsyslog_files_permissions
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-rsyslog_files_permissions:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-89058-2

References:
ism0988, 1405
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
pcidssReq-10.5.1, Req-10.5.2
anssiR71
cis6.2.4.1
pcidss410.3.1, 10.3
Description
The file permissions for all log files written by rsyslog should be set to 640, or more restrictive. These log files are determined by the second part of each Rule line in /etc/rsyslog.conf and typically all appear in /var/log. For each log file LOGFILE referenced in /etc/rsyslog.conf, run the following command to inspect the file's permissions:
$ ls -l LOGFILE
        
If the permissions are not 640 or more restrictive, run the following command to correct this:
$ sudo chmod 640 LOGFILE
        
"
Rationale
Log files can contain valuable information regarding system configuration. If the system log files are not protected unauthorized users could change the logged data, eliminating their forensic value.
OVAL test results details

System log files have appropriate permissions set  oval:ssg-test_rsyslog_files_permissions:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
true/var/log/maillogregular000rw------- 
true/var/log/cloud-init.logregular00259538rw-r----- 
true/var/log/secureregular0025638rw------- 
true/var/log/cronregular003550rw------- 
true/var/log/spoolerregular000rw------- 
true/var/log/messagesregular00337290rw------- 
Install systemd-journal-remote Packagexccdf_org.ssgproject.content_rule_package_systemd-journal-remote_installed mediumCCE-89465-9

Install systemd-journal-remote Package

Rule IDxccdf_org.ssgproject.content_rule_package_systemd-journal-remote_installed
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-package_systemd-journal-remote_installed:def:1
Time2025-07-22T08:31:41+00:00
Severitymedium
Identifiers:

CCE-89465-9

References:
os-srgSRG-OS-000479-GPOS-00224
cis6.2.2.1.1
Description
Journald (via systemd-journal-remote ) supports the ability to send log events it gathers to a remote log host or to receive messages from remote hosts, thus enabling centralised log management.
Rationale
Storing log data on a remote host protects log integrity from local attacks. If an attacker gains root access on the local system, they could tamper with or remove log data that is stored on the local system.

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if ! rpm -q --quiet "systemd-journal-remote" ; then
    dnf install -y "systemd-journal-remote"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89465-9
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_systemd-journal-remote_installed

- name: Ensure systemd-journal-remote is installed
  package:
    name: systemd-journal-remote
    state: present
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89465-9
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_systemd-journal-remote_installed

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
include install_systemd-journal-remote

class install_systemd-journal-remote {
  package { 'systemd-journal-remote':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

package --add=systemd-journal-remote


[[packages]]
name = "systemd-journal-remote"
version = "*"

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

package install systemd-journal-remote

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

dnf install systemd-journal-remote
OVAL test results details

package systemd-journal-remote is installed  oval:ssg-test_package_systemd-journal-remote_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_systemd-journal-remote_installed:obj:1 of type rpminfo_object
Name
systemd-journal-remote
Enable systemd-journald Servicexccdf_org.ssgproject.content_rule_service_systemd-journald_enabled mediumCCE-89396-6

Enable systemd-journald Service

Rule IDxccdf_org.ssgproject.content_rule_service_systemd-journald_enabled
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-service_systemd-journald_enabled:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-89396-6

References:
nistSC-24
os-srgSRG-OS-000269-GPOS-00103
cis6.2.1.1
Description
The systemd-journald service is an essential component of systemd. The systemd-journald service can be enabled with the following command:
$ sudo systemctl enable systemd-journald.service
Rationale
In the event of a system failure, Red Hat Enterprise Linux 10 must preserve any information necessary to determine cause of failure and any information necessary to return to operations with least disruption to system processes.
OVAL test results details

package systemd is installed  oval:ssg-test_service_systemd-journald_package_systemd_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedsystemdx86_64(none)9.el10_0.12570:257-9.el10_0.1199e2f91fd431d51systemd-0:257-9.el10_0.1.x86_64

Test that the systemd-journald service is running  oval:ssg-test_service_running_systemd-journald:tst:1  true

Following items have been found on the system:
Result of item-state comparisonUnitPropertyValue
truesystemd-journald.socketActiveStateactive
truesystemd-journald.serviceActiveStateactive

systemd test  oval:ssg-test_multi_user_wants_systemd-journald:tst:1  true

Following items have been found on the system:
Result of item-state comparisonUnitDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependency
truemulti-user.targetbasic.targetsysinit.targetlvm2-lvmpolld.socketswap.targetdev-mqueue.mountsystemd-update-utmp.servicesys-kernel-tracing.mountsystemd-hibernate-clear.servicesystemd-udevd.servicesystemd-update-done.servicesystemd-journal-catalog-update.servicesystemd-ask-password-console.pathsystemd-boot-random-seed.servicesystemd-network-generator.serviceintegritysetup.targetsystemd-confext.servicesystemd-firstboot.servicesystemd-tpm2-setup.servicelvm2-monitor.servicesystemd-tmpfiles-setup-dev-early.servicesystemd-sysusers.serviceproc-sys-fs-binfmt_misc.automountldconfig.servicesystemd-machine-id-commit.servicesystemd-repart.servicecryptsetup.targetfips-crypto-policy-overlay.servicesystemd-pstore.serviceselinux-autorelabel-mark.servicesystemd-random-seed.servicedracut-shutdown.servicesystemd-sysext.servicesys-kernel-debug.mountsystemd-journald.servicedev-hugepages.mountveritysetup.targetsys-fs-fuse-connections.mountsystemd-journal-flush.servicesystemd-tmpfiles-setup.servicesystemd-modules-load.servicelocal-fs.target-.mountboot-efi.mountsystemd-remount-fs.servicesystemd-udev-trigger.servicesystemd-tpm2-setup-early.servicesystemd-pcrphase-sysinit.servicesystemd-sysctl.servicekmod-static-nodes.servicesystemd-pcrphase.servicesystemd-hwdb-update.servicesystemd-tmpfiles-setup-dev.servicesystemd-binfmt.servicesys-kernel-config.mountsystemd-pcrmachine.servicetimers.targetfstrim.timerdnf-makecache.timerlogrotate.timernm-cloud-setup.timerfwupd-refresh.timersystemd-tmpfiles-clean.timerraid-check.timerslices.target-.slicesystem.slicepaths.targetsockets.targetsystemd-coredump.socketsystemd-userdbd.socketsystemd-journald.socketsystemd-pcrextend.socketsshd-unix-local.socketsystemd-creds.socketsystemd-pcrlock.socketpcscd.socketsystemd-sysext.socketsystemd-udevd-kernel.socketsssd-kcm.socketdbus.socketsshd-vsock.socketsystemd-hostnamed.socketsystemd-bootctl.socketsystemd-udevd-control.socketsystemd-journald-dev-log.socketdm-event.socketsystemd-initctl.socketsystemd-logind.serviceinsights-unregister.pathsshd.servicecloud-init.targetcloud-config.servicecloud-init.servicecloud-final.servicecloud-init-local.servicesssd.serviceaudit-rules.servicerhcd.pathremote-fs.targetinsights-client-boot.servicerhsmcertd.serviceinsights-unregistered.pathsystemd-user-sessions.servicetuned.serviceremote-cryptsetup.targetinsights-register.pathrhcd-stop.pathirqbalance.servicesystemd-ask-password-wall.pathrsyslog.servicegetty.targetgetty@tty1.serviceserial-getty@ttyS0.servicecrond.servicekdump.serviceauditd.serviceNetworkManager.servicemdmonitor.servicesystemd-update-utmp-runlevel.servicechronyd.service

systemd test  oval:ssg-test_multi_user_wants_systemd-journald_socket:tst:1  true

Following items have been found on the system:
Result of item-state comparisonUnitDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependency
truemulti-user.targetbasic.targetsysinit.targetlvm2-lvmpolld.socketswap.targetdev-mqueue.mountsystemd-update-utmp.servicesys-kernel-tracing.mountsystemd-hibernate-clear.servicesystemd-udevd.servicesystemd-update-done.servicesystemd-journal-catalog-update.servicesystemd-ask-password-console.pathsystemd-boot-random-seed.servicesystemd-network-generator.serviceintegritysetup.targetsystemd-confext.servicesystemd-firstboot.servicesystemd-tpm2-setup.servicelvm2-monitor.servicesystemd-tmpfiles-setup-dev-early.servicesystemd-sysusers.serviceproc-sys-fs-binfmt_misc.automountldconfig.servicesystemd-machine-id-commit.servicesystemd-repart.servicecryptsetup.targetfips-crypto-policy-overlay.servicesystemd-pstore.serviceselinux-autorelabel-mark.servicesystemd-random-seed.servicedracut-shutdown.servicesystemd-sysext.servicesys-kernel-debug.mountsystemd-journald.servicedev-hugepages.mountveritysetup.targetsys-fs-fuse-connections.mountsystemd-journal-flush.servicesystemd-tmpfiles-setup.servicesystemd-modules-load.servicelocal-fs.target-.mountboot-efi.mountsystemd-remount-fs.servicesystemd-udev-trigger.servicesystemd-tpm2-setup-early.servicesystemd-pcrphase-sysinit.servicesystemd-sysctl.servicekmod-static-nodes.servicesystemd-pcrphase.servicesystemd-hwdb-update.servicesystemd-tmpfiles-setup-dev.servicesystemd-binfmt.servicesys-kernel-config.mountsystemd-pcrmachine.servicetimers.targetfstrim.timerdnf-makecache.timerlogrotate.timernm-cloud-setup.timerfwupd-refresh.timersystemd-tmpfiles-clean.timerraid-check.timerslices.target-.slicesystem.slicepaths.targetsockets.targetsystemd-coredump.socketsystemd-userdbd.socketsystemd-journald.socketsystemd-pcrextend.socketsshd-unix-local.socketsystemd-creds.socketsystemd-pcrlock.socketpcscd.socketsystemd-sysext.socketsystemd-udevd-kernel.socketsssd-kcm.socketdbus.socketsshd-vsock.socketsystemd-hostnamed.socketsystemd-bootctl.socketsystemd-udevd-control.socketsystemd-journald-dev-log.socketdm-event.socketsystemd-initctl.socketsystemd-logind.serviceinsights-unregister.pathsshd.servicecloud-init.targetcloud-config.servicecloud-init.servicecloud-final.servicecloud-init-local.servicesssd.serviceaudit-rules.servicerhcd.pathremote-fs.targetinsights-client-boot.servicerhsmcertd.serviceinsights-unregistered.pathsystemd-user-sessions.servicetuned.serviceremote-cryptsetup.targetinsights-register.pathrhcd-stop.pathirqbalance.servicesystemd-ask-password-wall.pathrsyslog.servicegetty.targetgetty@tty1.serviceserial-getty@ttyS0.servicecrond.servicekdump.serviceauditd.serviceNetworkManager.servicemdmonitor.servicesystemd-update-utmp-runlevel.servicechronyd.service
Ensure journald is configured to compress large log filesxccdf_org.ssgproject.content_rule_journald_compress mediumCCE-87639-1

Ensure journald is configured to compress large log files

Rule IDxccdf_org.ssgproject.content_rule_journald_compress
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-journald_compress:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-87639-1

References:
cis6.2.2.3
Description
The journald system can compress large log files to avoid fill the system disk.
Rationale
Log files that are not properly compressed run the risk of growing so large that they fill up the log partition. Valuable logging information could be lost if the log partition becomes full.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

found=false

# set value in all files if they contain section or key
for f in $(echo -n "/etc/systemd/journald.conf.d/complianceascode_hardening.conf /etc/systemd/journald.conf.d/*.conf /etc/systemd/journald.conf"); do
    if [ ! -e "$f" ]; then
        continue
    fi

    # find key in section and change value
    if grep -qzosP "[[:space:]]*\[Journal\]([^\n\[]*\n+)+?[[:space:]]*Compress" "$f"; then

            sed -i "s/Compress[^(\n)]*/Compress=yes/" "$f"

            found=true

    # find section and add key = value to it
    elif grep -qs "[[:space:]]*\[Journal\]" "$f"; then

            sed -i "/[[:space:]]*\[Journal\]/a Compress=yes" "$f"

            found=true
    fi
done

# if section not in any file, append section with key = value to FIRST file in files parameter
if ! $found ; then
    file=$(echo "/etc/systemd/journald.conf.d/complianceascode_hardening.conf /etc/systemd/journald.conf.d/*.conf /etc/systemd/journald.conf" | cut -f1 -d ' ')
    mkdir -p "$(dirname "$file")"

    echo -e "[Journal]\nCompress=yes" >> "$file"

fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87639-1
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to compress large log files - Search for a section
    in files
  ansible.builtin.find:
    paths: '{{item.path}}'
    patterns: '{{item.pattern}}'
    contains: ^\s*\[Journal\]
    read_whole_file: true
    use_regex: true
  register: systemd_dropin_files_with_section
  loop:
  - path: '{{ ''/etc/systemd/journald.conf'' | dirname }}'
    pattern: '{{ ''/etc/systemd/journald.conf'' | basename | regex_escape }}'
  - path: /etc/systemd/journald.conf.d
    pattern: .*\.conf
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87639-1
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to compress large log files - Count number of
    files which contain the correct section
  ansible.builtin.set_fact:
    count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
      | map(attribute=''matched'') | list | map(''int'') | sum}}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87639-1
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to compress large log files - Add missing configuration
    to correct section
  community.general.ini_file:
    path: '{{item}}'
    section: Journal
    option: Compress
    value: 'yes'
    state: present
    no_extra_spaces: true
  when:
  - '"kernel" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int > 0
  loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
    | map(attribute=''path'') | list }}'
  tags:
  - CCE-87639-1
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to compress large log files - Add configuration
    to new remediation file
  community.general.ini_file:
    path: /etc/systemd/journald.conf.d/complianceascode_hardening.conf
    section: Journal
    option: Compress
    value: 'yes'
    state: present
    no_extra_spaces: true
    create: true
  when:
  - '"kernel" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int == 0
  tags:
  - CCE-87639-1
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

tests the value of Compress setting in the /etc/systemd/journald.conf file  oval:ssg-test_journald_compress:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_journald_compress:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/systemd/journald.conf^\s*\[Journal\].*(?:\n\s*[^[\s].*)*\n^[ \t]*Compress=(.+?)[ \t]*(?:$|#)1

tests the value of Compress setting in the /etc/systemd/journald.conf.d file  oval:ssg-test_journald_compress_config_dir:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_journald_compress_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/systemd/journald.conf.d.*\.conf$^\s*\[Journal\].*(?:\n\s*[^[\s].*)*\n^[ \t]*Compress=(.+?)[ \t]*(?:$|#)1
Ensure journald is configured to write log files to persistent diskxccdf_org.ssgproject.content_rule_journald_storage mediumCCE-90077-9

Ensure journald is configured to write log files to persistent disk

Rule IDxccdf_org.ssgproject.content_rule_journald_storage
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-journald_storage:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-90077-9

References:
cis6.2.2.4
Description
The journald system may store log files in volatile memory or locally on disk. If the logs are only stored in volatile memory they will be lost upon reboot.
Rationale
Log files contain valuable data and need to be persistent to aid in possible investigations.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

found=false

# set value in all files if they contain section or key
for f in $(echo -n "/etc/systemd/journald.conf.d/complianceascode_hardening.conf /etc/systemd/journald.conf.d/*.conf /etc/systemd/journald.conf"); do
    if [ ! -e "$f" ]; then
        continue
    fi

    # find key in section and change value
    if grep -qzosP "[[:space:]]*\[Journal\]([^\n\[]*\n+)+?[[:space:]]*Storage" "$f"; then

            sed -i "s/Storage[^(\n)]*/Storage=persistent/" "$f"

            found=true

    # find section and add key = value to it
    elif grep -qs "[[:space:]]*\[Journal\]" "$f"; then

            sed -i "/[[:space:]]*\[Journal\]/a Storage=persistent" "$f"

            found=true
    fi
done

# if section not in any file, append section with key = value to FIRST file in files parameter
if ! $found ; then
    file=$(echo "/etc/systemd/journald.conf.d/complianceascode_hardening.conf /etc/systemd/journald.conf.d/*.conf /etc/systemd/journald.conf" | cut -f1 -d ' ')
    mkdir -p "$(dirname "$file")"

    echo -e "[Journal]\nStorage=persistent" >> "$file"

fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90077-9
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to write log files to persistent disk - Search
    for a section in files
  ansible.builtin.find:
    paths: '{{item.path}}'
    patterns: '{{item.pattern}}'
    contains: ^\s*\[Journal\]
    read_whole_file: true
    use_regex: true
  register: systemd_dropin_files_with_section
  loop:
  - path: '{{ ''/etc/systemd/journald.conf'' | dirname }}'
    pattern: '{{ ''/etc/systemd/journald.conf'' | basename | regex_escape }}'
  - path: /etc/systemd/journald.conf.d
    pattern: .*\.conf
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90077-9
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to write log files to persistent disk - Count
    number of files which contain the correct section
  ansible.builtin.set_fact:
    count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
      | map(attribute=''matched'') | list | map(''int'') | sum}}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90077-9
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to write log files to persistent disk - Add
    missing configuration to correct section
  community.general.ini_file:
    path: '{{item}}'
    section: Journal
    option: Storage
    value: persistent
    state: present
    no_extra_spaces: true
  when:
  - '"kernel" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int > 0
  loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
    | map(attribute=''path'') | list }}'
  tags:
  - CCE-90077-9
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to write log files to persistent disk - Add
    configuration to new remediation file
  community.general.ini_file:
    path: /etc/systemd/journald.conf.d/complianceascode_hardening.conf
    section: Journal
    option: Storage
    value: persistent
    state: present
    no_extra_spaces: true
    create: true
  when:
  - '"kernel" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int == 0
  tags:
  - CCE-90077-9
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

tests the value of Storage setting in the /etc/systemd/journald.conf file  oval:ssg-test_journald_storage:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_journald_storage:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/systemd/journald.conf^\s*\[Journal\].*(?:\n\s*[^[\s].*)*\n^[ \t]*Storage=(.+?)[ \t]*(?:$|#)1

tests the value of Storage setting in the /etc/systemd/journald.conf.d file  oval:ssg-test_journald_storage_config_dir:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_journald_storage_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/systemd/journald.conf.d.*\.conf$^\s*\[Journal\].*(?:\n\s*[^[\s].*)*\n^[ \t]*Storage=(.+?)[ \t]*(?:$|#)1
Disable systemd-journal-remote Socketxccdf_org.ssgproject.content_rule_socket_systemd-journal-remote_disabled mediumCCE-87754-8

Disable systemd-journal-remote Socket

Rule IDxccdf_org.ssgproject.content_rule_socket_systemd-journal-remote_disabled
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-socket_systemd-journal-remote_disabled:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-87754-8

References:
cis6.2.2.1.4
Description
Journald supports the ability to receive messages from remote hosts, thus acting as a log server. Clients should not receive data from other hosts. NOTE: The same package, systemd-journal-remote , is used for both sending logs to remote hosts and receiving incoming logs. With regards to receiving logs, there are two Systemd unit files; systemd-journal-remote.socket and systemd-journal-remote.service.
Rationale
If a client is configured to also receive data, thus turning it into a server, the client system is acting outside it's operational boundary.
OVAL test results details

Test that the property LoadState from the systemd-journal-remote.socket is masked  oval:ssg-test_socket_loadstate_is_masked_systemd-journal-remote:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_socket_loadstate_is_masked_systemd-journal-remote:obj:1 of type systemdunitproperty_object
UnitProperty
^systemd-journal-remote.socket$LoadState
Install firewalld Packagexccdf_org.ssgproject.content_rule_package_firewalld_installed mediumCCE-88164-9

Install firewalld Package

Rule IDxccdf_org.ssgproject.content_rule_package_firewalld_installed
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-package_firewalld_installed:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-88164-9

References:
nistCM-6(a)
osppFMT_SMF_EXT.1
os-srgSRG-OS-000096-GPOS-00050, SRG-OS-000297-GPOS-00115, SRG-OS-000298-GPOS-00116, SRG-OS-000480-GPOS-00227, SRG-OS-000480-GPOS-00232
cis4.1.2
pcidss41.2.1, 1.2
Description
The firewalld package can be installed with the following command:
$ sudo dnf install firewalld
Rationale
"Firewalld" provides an easy and effective way to block/limit remote access to the system via ports, services, and protocols. Remote access services, such as those providing remote access to network devices and information systems, which lack automated control capabilities, increase risk and make remote user access management difficult at best. Remote access is access to DoD nonpublic information systems by an authorized user (or an information system) communicating through an external, non-organization-controlled network. Remote access methods include, for example, dial-up, broadband, and wireless. Red Hat Enterprise Linux 10 functionality (e.g., SSH) must be capable of taking enforcement action if the audit reveals unauthorized activity. Automated control of remote access sessions allows organizations to ensure ongoing compliance with remote access policies by enforcing connection rules of remote access applications on a variety of information system components (e.g., servers, workstations, notebook computers, smartphones, and tablets)."

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if ! rpm -q --quiet "firewalld" ; then
    dnf install -y "firewalld"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88164-9
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-1.2
  - PCI-DSSv4-1.2.1
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_firewalld_installed

- name: Ensure firewalld is installed
  package:
    name: firewalld
    state: present
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88164-9
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-1.2
  - PCI-DSSv4-1.2.1
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_firewalld_installed

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
include install_firewalld

class install_firewalld {
  package { 'firewalld':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

package --add=firewalld


[[packages]]
name = "firewalld"
version = "*"

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

package install firewalld

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

dnf install firewalld
OVAL test results details

package firewalld is installed  oval:ssg-test_package_firewalld_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_firewalld_installed:obj:1 of type rpminfo_object
Name
firewalld
Verify firewalld Enabledxccdf_org.ssgproject.content_rule_service_firewalld_enabled mediumCCE-88110-2

Verify firewalld Enabled

Rule IDxccdf_org.ssgproject.content_rule_service_firewalld_enabled
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-88110-2

References:
cis-csc11, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05
cui3.1.3, 3.4.7
isa-62443-20094.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nerc-cipCIP-003-8 R4, CIP-003-8 R5, CIP-004-6 R3
nistAC-4, CM-7(b), CA-3(5), SC-7(21), CM-6(a)
nist-csfPR.IP-1
osppFMT_SMF_EXT.1
os-srgSRG-OS-000096-GPOS-00050, SRG-OS-000297-GPOS-00115, SRG-OS-000480-GPOS-00227, SRG-OS-000480-GPOS-00231, SRG-OS-000480-GPOS-00232
bsiSYS.1.6.A5, SYS.1.6.A21
cis4.1.2
pcidss41.2.1, 1.2
Description
The firewalld service can be enabled with the following command:
$ sudo systemctl enable firewalld.service
Rationale
Access control methods provide the ability to enhance system security posture by restricting services and known good IP addresses and address ranges. This prevents connections from unknown hosts and protocols.
Configure Firewalld to Restrict Loopback Trafficxccdf_org.ssgproject.content_rule_firewalld_loopback_traffic_restricted mediumCCE-89320-6

Configure Firewalld to Restrict Loopback Traffic

Rule IDxccdf_org.ssgproject.content_rule_firewalld_loopback_traffic_restricted
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-firewalld_loopback_traffic_restricted:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-89320-6

References:
cis4.2.2
pcidss41.4.1, 1.4
Description
Configure firewalld to restrict loopback traffic to the lo interface. The loopback traffic must be trusted by assigning the lo interface to the firewalld trusted zone. However, the loopback traffic must be restricted to the loopback interface as an anti-spoofing measure. To configure firewalld to restrict loopback traffic to the lo interface, run the following commands:
sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv4 source address="127.0.0.1" destination not address="127.0.0.1" drop'
sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv6 source address="::1" destination not address="::1" drop'
To ensure firewalld settings are applied in runtime, run the following command:
firewall-cmd --reload
Rationale
Loopback traffic is generated between processes on machine and is typically critical to operation of the system. The loopback interface is the only place that loopback network traffic should be seen, all other interfaces should ignore traffic on this network as an anti-spoofing measure.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if ! rpm -q --quiet "firewalld" ; then
    dnf install -y "firewalld"
fi

ipv4_rule='rule family=ipv4 source address="127.0.0.1" destination not address="127.0.0.1" drop'
ipv6_rule='rule family=ipv6 source address="::1" destination not address="::1" drop'

if test "$(stat -c %d:%i /)" != "$(stat -c %d:%i /proc/1/root/.)" || { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; }; then
    firewall-offline-cmd --zone=trusted --add-rich-rule="${ipv4_rule}"
    firewall-offline-cmd --zone=trusted --add-rich-rule="${ipv6_rule}"
elif systemctl is-active firewalld; then
    firewall-cmd --permanent --zone=trusted --add-rich-rule="${ipv4_rule}"
    firewall-cmd --permanent --zone=trusted --add-rich-rule="${ipv6_rule}"
    firewall-cmd --reload
else
    echo "
    firewalld service is not active. Remediation aborted!
    This remediation could not be applied because it depends on firewalld service running.
    The service is not started by this remediation in order to prevent connection issues."
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89320-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_restricted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Firewalld to Restrict Loopback Traffic - Ensure firewalld Package
    is Installed
  ansible.builtin.package:
    name: '{{ item }}'
    state: present
  with_items:
  - firewalld
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89320-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_restricted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Firewalld to Restrict Loopback Traffic - Collect Facts About System
    Services
  ansible.builtin.service_facts: null
  register: result_services_states
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89320-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_restricted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Firewalld to Restrict Loopback Traffic - Remediation is Applicable
    if firewalld Service is Running
  block:

  - name: Configure Firewalld to Restrict Loopback Traffic - Ensure firewalld trusted
      Zone Restricts IPv4 Loopback Traffic
    ansible.builtin.command:
      cmd: firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv4
        source address="127.0.0.1" destination not address="127.0.0.1" drop'
    register: result_trusted_ipv4_restriction
    changed_when:
    - '''ALREADY_ENABLED'' not in result_trusted_ipv4_restriction.stderr'

  - name: Configure Firewalld to Restrict Loopback Traffic - Ensure firewalld trusted
      Zone Restricts IPv6 Loopback Traffic
    ansible.builtin.command:
      cmd: firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv6
        source address="::1" destination not address="::1" drop'
    register: result_trusted_ipv6_restriction
    changed_when:
    - '''ALREADY_ENABLED'' not in result_trusted_ipv6_restriction.stderr'

  - name: Configure Firewalld to Restrict Loopback Traffic - Ensure firewalld Changes
      are Applied
    ansible.builtin.service:
      name: firewalld
      state: reloaded
    when:
    - result_trusted_ipv4_restriction is changed or result_trusted_ipv6_restriction
      is changed
  when:
  - '"kernel" in ansible_facts.packages'
  - ansible_facts.services['firewalld.service'].state == 'running'
  tags:
  - CCE-89320-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_restricted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Firewalld to Restrict Loopback Traffic - Informative Message Based
    on Service State
  ansible.builtin.assert:
    that:
    - ansible_facts.services['firewalld.service'].state == 'running'
    fail_msg:
    - firewalld service is not active. Remediation aborted!
    - This remediation could not be applied because it depends on firewalld service
      running.
    - The service is not started by this remediation in order to prevent connection
      issues.
    success_msg:
    - Configure Firewalld to Restrict Loopback Traffic remediation successfully executed
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89320-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_restricted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

there is no equivalent file for trusted zone defined by the administrator  oval:ssg-test_firewalld_trusted_zone_not_overridden:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_customized_trusted_zone_file:obj:1 of type file_object
Filepath
/etc/firewalld/zones/trusted.xml

default trusted zone has rich-rule to restrict loopback source  oval:ssg-test_firewalld_loopback_restricted_source_usr:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_loopback_restricted_source_usr:obj:1 of type xmlfilecontent_object
FilepathXpath
/usr/lib/firewalld/zones/trusted.xml/zone/rule/source[@address='127.0.0.1' or @address='::1']

default trusted zone has rich-rule to restrict loopback destination  oval:ssg-test_firewalld_loopback_restricted_destination_usr:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_loopback_restricted_destination_usr:obj:1 of type xmlfilecontent_object
FilepathXpath
/usr/lib/firewalld/zones/trusted.xml/zone/rule/destination[@address='127.0.0.1' or @address='::1' and @invert='True']

default trusted zone has rich-rule to restrict loopback traffic  oval:ssg-test_firewalld_loopback_restricted_policy_usr:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_loopback_restricted_policy_usr:obj:1 of type xmlfilecontent_object
FilepathXpath
/usr/lib/firewalld/zones/trusted.xml/zone/rule/drop

custom trusted zone has rich-rule to restrict loopback source  oval:ssg-test_firewalld_loopback_restricted_source_etc:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_loopback_restricted_source_etc:obj:1 of type xmlfilecontent_object
FilepathXpath
/etc/firewalld/zones/trusted.xml/zone/rule/source[@address='127.0.0.1' or @address='::1']

custom trusted zone has rich-rule to restrict loopback destination  oval:ssg-test_firewalld_loopback_restricted_destination_etc:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_loopback_restricted_destination_etc:obj:1 of type xmlfilecontent_object
FilepathXpath
/etc/firewalld/zones/trusted.xml/zone/rule/destination[@address='127.0.0.1' or @address='::1' and @invert='True']

custom trusted zone has rich-rule to restrict loopback traffic  oval:ssg-test_firewalld_loopback_restricted_policy_etc:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_loopback_restricted_policy_etc:obj:1 of type xmlfilecontent_object
FilepathXpath
/etc/firewalld/zones/trusted.xml/zone/rule/drop
Configure Firewalld to Trust Loopback Trafficxccdf_org.ssgproject.content_rule_firewalld_loopback_traffic_trusted mediumCCE-90267-6

Configure Firewalld to Trust Loopback Traffic

Rule IDxccdf_org.ssgproject.content_rule_firewalld_loopback_traffic_trusted
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-firewalld_loopback_traffic_trusted:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-90267-6

References:
cis4.2.2
pcidss41.4.1, 1.4
Description
Assign loopback interface to the firewalld trusted zone in order to explicitly allow the loopback traffic in the system. To configure firewalld to trust loopback traffic, run the following command:
sudo firewall-cmd --permanent --zone=trusted --add-interface=lo
To ensure firewalld settings are applied in runtime, run the following command:
firewall-cmd --reload
Rationale
Loopback traffic is generated between processes on machine and is typically critical to operation of the system. The loopback interface is the only place that loopback network traffic should be seen, all other interfaces should ignore traffic on this network as an anti-spoofing measure.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if ! rpm -q --quiet "firewalld" ; then
    dnf install -y "firewalld"
fi

if test "$(stat -c %d:%i /)" != "$(stat -c %d:%i /proc/1/root/.)" || { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; }; then
    firewall-offline-cmd --zone=trusted --add-interface=lo
elif systemctl is-active firewalld; then
    firewall-cmd --permanent --zone=trusted --add-interface=lo
    firewall-cmd --reload
else
    echo "
    firewalld service is not active. Remediation aborted!
    This remediation could not be applied because it depends on firewalld service running.
    The service is not started by this remediation in order to prevent connection issues."
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90267-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_trusted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Firewalld to Trust Loopback Traffic - Ensure firewalld Package is
    Installed
  ansible.builtin.package:
    name: '{{ item }}'
    state: present
  with_items:
  - firewalld
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90267-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_trusted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Firewalld to Trust Loopback Traffic - Collect Facts About System
    Services
  ansible.builtin.service_facts: null
  register: result_services_states
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90267-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_trusted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Firewalld to Trust Loopback Traffic - Remediation is Applicable
    if firewalld Service is Running
  block:

  - name: Configure Firewalld to Trust Loopback Traffic - Ensure firewalld trusted
      Zone Includes lo Interface
    ansible.builtin.command:
      cmd: firewall-cmd --permanent --zone=trusted --add-interface=lo
    register: result_lo_interface_assignment
    changed_when:
    - '''ALREADY_ENABLED'' not in result_lo_interface_assignment.stderr'

  - name: Configure Firewalld to Trust Loopback Traffic - Ensure firewalld Changes
      are Applied
    ansible.builtin.service:
      name: firewalld
      state: reloaded
    when:
    - result_lo_interface_assignment is changed
  when:
  - '"kernel" in ansible_facts.packages'
  - ansible_facts.services['firewalld.service'].state == 'running'
  tags:
  - CCE-90267-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_trusted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Firewalld to Trust Loopback Traffic - Informative Message Based
    on Service State
  ansible.builtin.assert:
    that:
    - ansible_facts.services['firewalld.service'].state == 'running'
    fail_msg:
    - firewalld service is not active. Remediation aborted!
    - This remediation could not be applied because it depends on firewalld service
      running.
    - The service is not started by this remediation in order to prevent connection
      issues.
    success_msg:
    - Configure Firewalld to Trust Loopback Traffic remediation successfully executed
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90267-6
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.1
  - configure_strategy
  - firewalld_loopback_traffic_trusted
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

lo interface is assigned to the trusted zone by default  oval:ssg-test_firewalld_lo_interface_trusted_usr:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_lo_interface_trusted_usr:obj:1 of type xmlfilecontent_object
FilepathXpath
/usr/lib/firewalld/zones/trusted.xml/zone/interface[@name='lo']

there is no equivalent file for trusted zone defined by the administrator  oval:ssg-test_firewalld_trusted_zone_not_overridden:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_customized_trusted_zone_file:obj:1 of type file_object
Filepath
/etc/firewalld/zones/trusted.xml

lo interface is assigned to the custom trusted zone in /etc/firewalld/zones  oval:ssg-test_firewalld_lo_interface_trusted_etc:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_firewalld_lo_interface_trusted_etc:obj:1 of type xmlfilecontent_object
FilepathXpath
/etc/firewalld/zones/trusted.xml/zone/interface[@name='lo']
Configure Accepting Router Advertisements on All IPv6 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_accept_ra mediumCCE-88665-5

Configure Accepting Router Advertisements on All IPv6 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_accept_ra
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv6_conf_all_accept_ra:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-88665-5

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui3.1.20
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
cis3.3.11
Description
To set the runtime status of the net.ipv6.conf.all.accept_ra kernel parameter, run the following command:
$ sudo sysctl -w net.ipv6.conf.all.accept_ra=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv6.conf.all.accept_ra = 0
Rationale
An illicit router advertisement message could result in a man-in-the-middle attack.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv6.conf.all.accept_ra from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv6.conf.all.accept_ra.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv6.conf.all.accept_ra" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv6_conf_all_accept_ra_value='0'


#
# Set runtime for net.ipv6.conf.all.accept_ra
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv6.conf.all.accept_ra="$sysctl_net_ipv6_conf_all_accept_ra_value"
fi

#
# If net.ipv6.conf.all.accept_ra present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv6.conf.all.accept_ra = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv6.conf.all.accept_ra")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_all_accept_ra_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv6.conf.all.accept_ra\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.all.accept_ra\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-88665-5"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88665-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_ra

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv6.conf.all.accept_ra.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88665-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_ra

- name: Comment out any occurrences of net.ipv6.conf.all.accept_ra from config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv6.conf.all.accept_ra
    replace: '#net.ipv6.conf.all.accept_ra'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88665-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_ra
- name: XCCDF Value sysctl_net_ipv6_conf_all_accept_ra_value # promote to variable
  set_fact:
    sysctl_net_ipv6_conf_all_accept_ra_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv6.conf.all.accept_ra is set
  sysctl:
    name: net.ipv6.conf.all.accept_ra
    value: '{{ sysctl_net_ipv6_conf_all_accept_ra_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88665-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_ra

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv6.conf.all.accept_ra%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_all_accept_ra.conf
        overwrite: true
OVAL test results details

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.disable_ipv6[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.disable_ipv6 set to 1  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.all.disable_ipv60

net.ipv6.conf.all.accept_ra static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_ra_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_accept_ra:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_accept_ra:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_accept_ra:obj:1

net.ipv6.conf.all.accept_ra static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_ra_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_accept_ra:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_accept_ra:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_accept_ra:obj:1

net.ipv6.conf.all.accept_ra static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_ra_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_accept_ra:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.accept_ra[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.accept_ra set to the appropriate value  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_ra_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.all.accept_ra1
Disable Accepting ICMP Redirects for All IPv6 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_accept_redirects mediumCCE-90083-7

Disable Accepting ICMP Redirects for All IPv6 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_accept_redirects
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv6_conf_all_accept_redirects:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-90083-7

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui3.1.20
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a), CM-6(b), CM-6.1(iv)
nist-csfPR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
anssiR13
cis3.3.5
Description
To set the runtime status of the net.ipv6.conf.all.accept_redirects kernel parameter, run the following command:
$ sudo sysctl -w net.ipv6.conf.all.accept_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv6.conf.all.accept_redirects = 0
Rationale
An illicit ICMP redirect message could result in a man-in-the-middle attack.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv6.conf.all.accept_redirects from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv6.conf.all.accept_redirects.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv6.conf.all.accept_redirects" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv6_conf_all_accept_redirects_value='0'


#
# Set runtime for net.ipv6.conf.all.accept_redirects
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv6.conf.all.accept_redirects="$sysctl_net_ipv6_conf_all_accept_redirects_value"
fi

#
# If net.ipv6.conf.all.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv6.conf.all.accept_redirects = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv6.conf.all.accept_redirects")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_all_accept_redirects_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv6.conf.all.accept_redirects\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.all.accept_redirects\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-90083-7"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90083-7
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_redirects

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv6.conf.all.accept_redirects.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90083-7
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_redirects

- name: Comment out any occurrences of net.ipv6.conf.all.accept_redirects from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv6.conf.all.accept_redirects
    replace: '#net.ipv6.conf.all.accept_redirects'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90083-7
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_redirects
- name: XCCDF Value sysctl_net_ipv6_conf_all_accept_redirects_value # promote to variable
  set_fact:
    sysctl_net_ipv6_conf_all_accept_redirects_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv6.conf.all.accept_redirects is set
  sysctl:
    name: net.ipv6.conf.all.accept_redirects
    value: '{{ sysctl_net_ipv6_conf_all_accept_redirects_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90083-7
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_redirects

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv6.conf.all.accept_redirects%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_all_accept_redirects.conf
        overwrite: true
OVAL test results details

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.disable_ipv6[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.disable_ipv6 set to 1  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.all.disable_ipv60

net.ipv6.conf.all.accept_redirects static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_redirects_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_accept_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_accept_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_accept_redirects:obj:1

net.ipv6.conf.all.accept_redirects static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_redirects_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_accept_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_accept_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_accept_redirects:obj:1

net.ipv6.conf.all.accept_redirects static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_redirects_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_accept_redirects:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.accept_redirects[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.accept_redirects set to the appropriate value  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_redirects_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.all.accept_redirects1
Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_accept_source_route mediumCCE-90450-8

Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_accept_source_route
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv6_conf_all_accept_source_route:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-90450-8

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 4, 6, 8, 9
cobit5APO01.06, APO13.01, DSS01.05, DSS03.01, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfDE.AE-1, ID.AM-3, PR.AC-5, PR.DS-5, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR13
cis3.3.8
Description
To set the runtime status of the net.ipv6.conf.all.accept_source_route kernel parameter, run the following command:
$ sudo sysctl -w net.ipv6.conf.all.accept_source_route=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv6.conf.all.accept_source_route = 0
Rationale
Source-routed packets allow the source of the packet to suggest routers forward the packet along a different path than configured on the router, which can be used to bypass network security measures. This requirement applies only to the forwarding of source-routerd traffic, such as when IPv6 forwarding is enabled and the system is functioning as a router.

Accepting source-routed packets in the IPv6 protocol has few legitimate uses. It should be disabled unless it is absolutely required.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv6.conf.all.accept_source_route from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv6.conf.all.accept_source_route.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv6.conf.all.accept_source_route" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv6_conf_all_accept_source_route_value='0'


#
# Set runtime for net.ipv6.conf.all.accept_source_route
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv6.conf.all.accept_source_route="$sysctl_net_ipv6_conf_all_accept_source_route_value"
fi

#
# If net.ipv6.conf.all.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv6.conf.all.accept_source_route = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv6.conf.all.accept_source_route")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_all_accept_source_route_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv6.conf.all.accept_source_route\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.all.accept_source_route\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-90450-8"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90450-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_source_route

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv6.conf.all.accept_source_route.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90450-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_source_route

- name: Comment out any occurrences of net.ipv6.conf.all.accept_source_route from
    config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv6.conf.all.accept_source_route
    replace: '#net.ipv6.conf.all.accept_source_route'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90450-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_source_route
- name: XCCDF Value sysctl_net_ipv6_conf_all_accept_source_route_value # promote to variable
  set_fact:
    sysctl_net_ipv6_conf_all_accept_source_route_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv6.conf.all.accept_source_route is set
  sysctl:
    name: net.ipv6.conf.all.accept_source_route
    value: '{{ sysctl_net_ipv6_conf_all_accept_source_route_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90450-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_accept_source_route

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv6.conf.all.accept_source_route%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_all_accept_source_route.conf
        overwrite: true
OVAL test results details

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.disable_ipv6[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.disable_ipv6 set to 1  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.all.disable_ipv60

net.ipv6.conf.all.accept_source_route static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_source_route_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_accept_source_route:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_accept_source_route:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_accept_source_route:obj:1

net.ipv6.conf.all.accept_source_route static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_source_route_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_accept_source_route:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_accept_source_route:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_accept_source_route:obj:1

net.ipv6.conf.all.accept_source_route static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_source_route_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_accept_source_route:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.accept_source_route[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.accept_source_route set to the appropriate value  oval:ssg-test_sysctl_net_ipv6_conf_all_accept_source_route_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv6.conf.all.accept_source_route0
Disable Kernel Parameter for IPv6 Forwardingxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_forwarding mediumCCE-86882-8

Disable Kernel Parameter for IPv6 Forwarding

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_forwarding
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv6_conf_all_forwarding:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-86882-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS05.02, DSS05.05, DSS05.07, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a), CM-6(b), CM-6.1(iv)
nist-csfDE.CM-1, PR.DS-4, PR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
cis3.3.1
Description
To set the runtime status of the net.ipv6.conf.all.forwarding kernel parameter, run the following command:
$ sudo sysctl -w net.ipv6.conf.all.forwarding=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv6.conf.all.forwarding = 0
Rationale
IP forwarding permits the kernel to forward packets from one network interface to another. The ability to forward packets between two networks is only appropriate for systems acting as routers.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv6.conf.all.forwarding from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv6.conf.all.forwarding.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv6.conf.all.forwarding" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv6_conf_all_forwarding_value='0'


#
# Set runtime for net.ipv6.conf.all.forwarding
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv6.conf.all.forwarding="$sysctl_net_ipv6_conf_all_forwarding_value"
fi

#
# If net.ipv6.conf.all.forwarding present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv6.conf.all.forwarding = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv6.conf.all.forwarding")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_all_forwarding_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv6.conf.all.forwarding\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.all.forwarding\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-86882-8"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86882-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_forwarding

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv6.conf.all.forwarding.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86882-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_forwarding

- name: Comment out any occurrences of net.ipv6.conf.all.forwarding from config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv6.conf.all.forwarding
    replace: '#net.ipv6.conf.all.forwarding'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86882-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_forwarding
- name: XCCDF Value sysctl_net_ipv6_conf_all_forwarding_value # promote to variable
  set_fact:
    sysctl_net_ipv6_conf_all_forwarding_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv6.conf.all.forwarding is set
  sysctl:
    name: net.ipv6.conf.all.forwarding
    value: '{{ sysctl_net_ipv6_conf_all_forwarding_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86882-8
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_all_forwarding
OVAL test results details

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.disable_ipv6[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.disable_ipv6 set to 1  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.all.disable_ipv60

net.ipv6.conf.all.forwarding static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_forwarding_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_forwarding:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_forwarding:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_forwarding:obj:1

net.ipv6.conf.all.forwarding static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_forwarding_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_forwarding:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_forwarding:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_forwarding:obj:1

net.ipv6.conf.all.forwarding static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_forwarding_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_forwarding:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.forwarding[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.forwarding set to the appropriate value  oval:ssg-test_sysctl_net_ipv6_conf_all_forwarding_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv6.conf.all.forwarding0
Disable Accepting Router Advertisements on all IPv6 Interfaces by Defaultxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_accept_ra mediumCCE-90557-0

Disable Accepting Router Advertisements on all IPv6 Interfaces by Default

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_accept_ra
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv6_conf_default_accept_ra:def:1
Time2025-07-22T08:31:43+00:00
Severitymedium
Identifiers:

CCE-90557-0

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui3.1.20
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
cis3.3.11
Description
To set the runtime status of the net.ipv6.conf.default.accept_ra kernel parameter, run the following command:
$ sudo sysctl -w net.ipv6.conf.default.accept_ra=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv6.conf.default.accept_ra = 0
Rationale
An illicit router advertisement message could result in a man-in-the-middle attack.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv6.conf.default.accept_ra from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv6.conf.default.accept_ra.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv6.conf.default.accept_ra" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv6_conf_default_accept_ra_value='0'


#
# Set runtime for net.ipv6.conf.default.accept_ra
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv6.conf.default.accept_ra="$sysctl_net_ipv6_conf_default_accept_ra_value"
fi

#
# If net.ipv6.conf.default.accept_ra present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv6.conf.default.accept_ra = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv6.conf.default.accept_ra")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_default_accept_ra_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv6.conf.default.accept_ra\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.default.accept_ra\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-90557-0"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90557-0
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_ra

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv6.conf.default.accept_ra.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90557-0
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_ra

- name: Comment out any occurrences of net.ipv6.conf.default.accept_ra from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv6.conf.default.accept_ra
    replace: '#net.ipv6.conf.default.accept_ra'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90557-0
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_ra
- name: XCCDF Value sysctl_net_ipv6_conf_default_accept_ra_value # promote to variable
  set_fact:
    sysctl_net_ipv6_conf_default_accept_ra_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv6.conf.default.accept_ra is set
  sysctl:
    name: net.ipv6.conf.default.accept_ra
    value: '{{ sysctl_net_ipv6_conf_default_accept_ra_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90557-0
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_ra

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv6.conf.default.accept_ra%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_default_accept_ra.conf
        overwrite: true
OVAL test results details

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.disable_ipv6[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.disable_ipv6 set to 1  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.all.disable_ipv60

net.ipv6.conf.default.accept_ra static configuration  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_ra_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_default_accept_ra:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_default_accept_ra:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_default_accept_ra:obj:1

net.ipv6.conf.default.accept_ra static configuration  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_ra_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_default_accept_ra:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_default_accept_ra:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_default_accept_ra:obj:1

net.ipv6.conf.default.accept_ra static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_ra_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_default_accept_ra:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.default.accept_ra[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.default.accept_ra set to the appropriate value  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_ra_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.default.accept_ra1
Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_accept_redirects mediumCCE-89486-5

Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_accept_redirects
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv6_conf_default_accept_redirects:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-89486-5

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui3.1.20
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
anssiR13
cis3.3.5
Description
To set the runtime status of the net.ipv6.conf.default.accept_redirects kernel parameter, run the following command:
$ sudo sysctl -w net.ipv6.conf.default.accept_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv6.conf.default.accept_redirects = 0
Rationale
An illicit ICMP redirect message could result in a man-in-the-middle attack.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv6.conf.default.accept_redirects from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv6.conf.default.accept_redirects.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv6.conf.default.accept_redirects" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv6_conf_default_accept_redirects_value='0'


#
# Set runtime for net.ipv6.conf.default.accept_redirects
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv6.conf.default.accept_redirects="$sysctl_net_ipv6_conf_default_accept_redirects_value"
fi

#
# If net.ipv6.conf.default.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv6.conf.default.accept_redirects = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv6.conf.default.accept_redirects")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_default_accept_redirects_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv6.conf.default.accept_redirects\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.default.accept_redirects\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-89486-5"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89486-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_redirects

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv6.conf.default.accept_redirects.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89486-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_redirects

- name: Comment out any occurrences of net.ipv6.conf.default.accept_redirects from
    config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv6.conf.default.accept_redirects
    replace: '#net.ipv6.conf.default.accept_redirects'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89486-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_redirects
- name: XCCDF Value sysctl_net_ipv6_conf_default_accept_redirects_value # promote to variable
  set_fact:
    sysctl_net_ipv6_conf_default_accept_redirects_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv6.conf.default.accept_redirects is set
  sysctl:
    name: net.ipv6.conf.default.accept_redirects
    value: '{{ sysctl_net_ipv6_conf_default_accept_redirects_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89486-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_redirects

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv6.conf.default.accept_redirects%20%3D%200%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_default_accept_redirects.conf
        overwrite: true
OVAL test results details

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.disable_ipv6[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.disable_ipv6 set to 1  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.all.disable_ipv60

net.ipv6.conf.default.accept_redirects static configuration  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_redirects_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_default_accept_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_default_accept_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_default_accept_redirects:obj:1

net.ipv6.conf.default.accept_redirects static configuration  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_redirects_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_default_accept_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_default_accept_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_default_accept_redirects:obj:1

net.ipv6.conf.default.accept_redirects static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_redirects_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_default_accept_redirects:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.default.accept_redirects[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.default.accept_redirects set to the appropriate value  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_redirects_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.default.accept_redirects1
Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces by Defaultxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_accept_source_route mediumCCE-89135-8

Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces by Default

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_accept_source_route
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv6_conf_default_accept_source_route:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-89135-8

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 4, 6, 8, 9
cobit5APO01.06, APO13.01, DSS01.05, DSS03.01, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-7(a), CM-7(b), CM-6(a), CM-6(b), CM-6.1(iv)
nist-csfDE.AE-1, ID.AM-3, PR.AC-5, PR.DS-5, PR.PT-4
pcidssReq-1.4.3
os-srgSRG-OS-000480-GPOS-00227
anssiR13
cis3.3.8
pcidss41.4.2, 1.4
Description
To set the runtime status of the net.ipv6.conf.default.accept_source_route kernel parameter, run the following command:
$ sudo sysctl -w net.ipv6.conf.default.accept_source_route=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv6.conf.default.accept_source_route = 0
Rationale
Source-routed packets allow the source of the packet to suggest routers forward the packet along a different path than configured on the router, which can be used to bypass network security measures. This requirement applies only to the forwarding of source-routerd traffic, such as when IPv6 forwarding is enabled and the system is functioning as a router. Accepting source-routed packets in the IPv6 protocol has few legitimate uses. It should be disabled unless it is absolutely required.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv6.conf.default.accept_source_route from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv6.conf.default.accept_source_route.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv6.conf.default.accept_source_route" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv6_conf_default_accept_source_route_value='0'


#
# Set runtime for net.ipv6.conf.default.accept_source_route
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv6.conf.default.accept_source_route="$sysctl_net_ipv6_conf_default_accept_source_route_value"
fi

#
# If net.ipv6.conf.default.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv6.conf.default.accept_source_route = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv6.conf.default.accept_source_route")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_default_accept_source_route_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv6.conf.default.accept_source_route\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.default.accept_source_route\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-89135-8"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89135-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_source_route

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv6.conf.default.accept_source_route.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89135-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_source_route

- name: Comment out any occurrences of net.ipv6.conf.default.accept_source_route from
    config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv6.conf.default.accept_source_route
    replace: '#net.ipv6.conf.default.accept_source_route'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89135-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_source_route
- name: XCCDF Value sysctl_net_ipv6_conf_default_accept_source_route_value # promote to variable
  set_fact:
    sysctl_net_ipv6_conf_default_accept_source_route_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv6.conf.default.accept_source_route is set
  sysctl:
    name: net.ipv6.conf.default.accept_source_route
    value: '{{ sysctl_net_ipv6_conf_default_accept_source_route_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89135-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv6_conf_default_accept_source_route

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv6.conf.default.accept_source_route%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_default_accept_source_route.conf
        overwrite: true
OVAL test results details

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1

net.ipv6.conf.all.disable_ipv6 static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_all_disable_ipv6:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.all.disable_ipv6[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.all.disable_ipv6 set to 1  oval:ssg-test_sysctl_net_ipv6_conf_all_disable_ipv6_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv6.conf.all.disable_ipv60

net.ipv6.conf.default.accept_source_route static configuration  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_source_route_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_default_accept_source_route:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_default_accept_source_route:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_default_accept_source_route:obj:1

net.ipv6.conf.default.accept_source_route static configuration  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_source_route_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv6_conf_default_accept_source_route:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv6_conf_default_accept_source_route:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv6_conf_default_accept_source_route:obj:1

net.ipv6.conf.default.accept_source_route static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_source_route_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv6_conf_default_accept_source_route:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv6.conf.default.accept_source_route[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv6.conf.default.accept_source_route set to the appropriate value  oval:ssg-test_sysctl_net_ipv6_conf_default_accept_source_route_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv6.conf.default.accept_source_route0
Disable Accepting ICMP Redirects for All IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_accept_redirects mediumCCE-90409-4

Disable Accepting ICMP Redirects for All IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_accept_redirects
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_all_accept_redirects:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-90409-4

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cjis5.10.1.1
cobit5APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS05.02, DSS05.05, DSS05.07, DSS06.06
cui3.1.20
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csfDE.CM-1, PR.DS-4, PR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.5
Description
To set the runtime status of the net.ipv4.conf.all.accept_redirects kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.all.accept_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.all.accept_redirects = 0
Rationale
ICMP redirect messages are used by routers to inform hosts that a more direct route exists for a particular destination. These messages modify the host's route table and are unauthenticated. An illicit ICMP redirect message could result in a man-in-the-middle attack.
This feature of the IPv4 protocol has few legitimate uses. It should be disabled unless absolutely required."

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.all.accept_redirects from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.all.accept_redirects.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.all.accept_redirects" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_conf_all_accept_redirects_value='0'


#
# Set runtime for net.ipv4.conf.all.accept_redirects
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.all.accept_redirects="$sysctl_net_ipv4_conf_all_accept_redirects_value"
fi

#
# If net.ipv4.conf.all.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.conf.all.accept_redirects = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.all.accept_redirects")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_accept_redirects_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.all.accept_redirects\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.accept_redirects\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-90409-4"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90409-4
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_accept_redirects

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.all.accept_redirects.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90409-4
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_accept_redirects

- name: Comment out any occurrences of net.ipv4.conf.all.accept_redirects from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.all.accept_redirects
    replace: '#net.ipv4.conf.all.accept_redirects'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90409-4
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_accept_redirects
- name: XCCDF Value sysctl_net_ipv4_conf_all_accept_redirects_value # promote to variable
  set_fact:
    sysctl_net_ipv4_conf_all_accept_redirects_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv4.conf.all.accept_redirects is set
  sysctl:
    name: net.ipv4.conf.all.accept_redirects
    value: '{{ sysctl_net_ipv4_conf_all_accept_redirects_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90409-4
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_accept_redirects

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.all.accept_redirects%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_accept_redirects.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.all.accept_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_accept_redirects_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_accept_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_accept_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_accept_redirects:obj:1

net.ipv4.conf.all.accept_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_accept_redirects_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_accept_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_accept_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_accept_redirects:obj:1

net.ipv4.conf.all.accept_redirects static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_all_accept_redirects_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_all_accept_redirects:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.all.accept_redirects[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.all.accept_redirects set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_conf_all_accept_redirects_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv4.conf.all.accept_redirects1
Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_accept_source_route mediumCCE-90165-2

Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_accept_source_route
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_all_accept_source_route:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-90165-2

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cobit5APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-7(a), CM-7(b), SC-5, CM-6(a), SC-7(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.8
Description
To set the runtime status of the net.ipv4.conf.all.accept_source_route kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.all.accept_source_route=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.all.accept_source_route = 0
Rationale
Source-routed packets allow the source of the packet to suggest routers forward the packet along a different path than configured on the router, which can be used to bypass network security measures. This requirement applies only to the forwarding of source-routerd traffic, such as when IPv4 forwarding is enabled and the system is functioning as a router.

Accepting source-routed packets in the IPv4 protocol has few legitimate uses. It should be disabled unless it is absolutely required.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.all.accept_source_route from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.all.accept_source_route.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.all.accept_source_route" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_conf_all_accept_source_route_value='0'


#
# Set runtime for net.ipv4.conf.all.accept_source_route
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.all.accept_source_route="$sysctl_net_ipv4_conf_all_accept_source_route_value"
fi

#
# If net.ipv4.conf.all.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.conf.all.accept_source_route = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.all.accept_source_route")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_accept_source_route_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.all.accept_source_route\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.accept_source_route\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-90165-2"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90165-2
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_accept_source_route

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.all.accept_source_route.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90165-2
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_accept_source_route

- name: Comment out any occurrences of net.ipv4.conf.all.accept_source_route from
    config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.all.accept_source_route
    replace: '#net.ipv4.conf.all.accept_source_route'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90165-2
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_accept_source_route
- name: XCCDF Value sysctl_net_ipv4_conf_all_accept_source_route_value # promote to variable
  set_fact:
    sysctl_net_ipv4_conf_all_accept_source_route_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv4.conf.all.accept_source_route is set
  sysctl:
    name: net.ipv4.conf.all.accept_source_route
    value: '{{ sysctl_net_ipv4_conf_all_accept_source_route_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90165-2
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_accept_source_route

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.all.accept_source_route%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_accept_source_route.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.all.accept_source_route static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_accept_source_route_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_accept_source_route:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_accept_source_route:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_accept_source_route:obj:1

net.ipv4.conf.all.accept_source_route static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_accept_source_route_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_accept_source_route:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_accept_source_route:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_accept_source_route:obj:1

net.ipv4.conf.all.accept_source_route static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_all_accept_source_route_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_all_accept_source_route:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.all.accept_source_route[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.all.accept_source_route set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_conf_all_accept_source_route_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv4.conf.all.accept_source_route0
Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_log_martians unknownCCE-89499-8

Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_log_martians
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_all_log_martians:def:1
Time2025-07-22T08:31:44+00:00
Severityunknown
Identifiers:

CCE-89499-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.04, DSS03.05, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.06
cui3.1.20
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.11.2.6, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.2.1, A.6.2.2, A.9.1.2
nistCM-7(a), CM-7(b), SC-5(3)(a)
nist-csfDE.CM-1, PR.AC-3, PR.DS-4, PR.IP-1, PR.PT-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
cis3.3.9
Description
To set the runtime status of the net.ipv4.conf.all.log_martians kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.all.log_martians=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.all.log_martians = 1
Rationale
The presence of "martian" packets (which have impossible addresses) as well as spoofed packets, source-routed packets, and redirects could be a sign of nefarious network activity. Logging these packets enables this activity to be detected.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.all.log_martians from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.all.log_martians.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.all.log_martians" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_conf_all_log_martians_value='1'


#
# Set runtime for net.ipv4.conf.all.log_martians
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.all.log_martians="$sysctl_net_ipv4_conf_all_log_martians_value"
fi

#
# If net.ipv4.conf.all.log_martians present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.conf.all.log_martians = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.all.log_martians")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_log_martians_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.all.log_martians\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.log_martians\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-89499-8"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89499-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(3)(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_conf_all_log_martians
  - unknown_severity

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.all.log_martians.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89499-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(3)(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_conf_all_log_martians
  - unknown_severity

- name: Comment out any occurrences of net.ipv4.conf.all.log_martians from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.all.log_martians
    replace: '#net.ipv4.conf.all.log_martians'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89499-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(3)(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_conf_all_log_martians
  - unknown_severity
- name: XCCDF Value sysctl_net_ipv4_conf_all_log_martians_value # promote to variable
  set_fact:
    sysctl_net_ipv4_conf_all_log_martians_value: !!str 1
  tags:
    - always

- name: Ensure sysctl net.ipv4.conf.all.log_martians is set
  sysctl:
    name: net.ipv4.conf.all.log_martians
    value: '{{ sysctl_net_ipv4_conf_all_log_martians_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89499-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(3)(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_conf_all_log_martians
  - unknown_severity

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.all.log_martians%3D1%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_log_martians.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.all.log_martians static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_log_martians_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_log_martians:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_log_martians:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_log_martians:obj:1

net.ipv4.conf.all.log_martians static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_log_martians_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_log_martians:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_log_martians:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_log_martians:obj:1

net.ipv4.conf.all.log_martians static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_all_log_martians_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_all_log_martians:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.all.log_martians[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.all.log_martians set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_conf_all_log_martians_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv4.conf.all.log_martians0
Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_rp_filter mediumCCE-88689-5

Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_rp_filter
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_all_rp_filter:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-88689-5

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 2, 4, 6, 7, 8, 9
cobit5APO01.06, APO13.01, BAI04.04, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.PT-4
pcidssReq-1.4.3
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.7
pcidss41.4.3, 1.4
Description
To set the runtime status of the net.ipv4.conf.all.rp_filter kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.all.rp_filter=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.all.rp_filter = 1
Rationale
Enabling reverse path filtering drops packets with source addresses that should not have been able to be received on the interface they were received on. It should not be used on systems which are routers for complicated networks, but is helpful for end hosts and routers serving small networks.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.all.rp_filter from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.all.rp_filter.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.all.rp_filter" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_conf_all_rp_filter_value='1'


#
# Set runtime for net.ipv4.conf.all.rp_filter
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.all.rp_filter="$sysctl_net_ipv4_conf_all_rp_filter_value"
fi

#
# If net.ipv4.conf.all.rp_filter present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.conf.all.rp_filter = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.all.rp_filter")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_rp_filter_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.all.rp_filter\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.rp_filter\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-88689-5"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88689-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_rp_filter

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.all.rp_filter.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88689-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_rp_filter

- name: Comment out any occurrences of net.ipv4.conf.all.rp_filter from config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.all.rp_filter
    replace: '#net.ipv4.conf.all.rp_filter'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88689-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_rp_filter
- name: XCCDF Value sysctl_net_ipv4_conf_all_rp_filter_value # promote to variable
  set_fact:
    sysctl_net_ipv4_conf_all_rp_filter_value: !!str 1
  tags:
    - always

- name: Ensure sysctl net.ipv4.conf.all.rp_filter is set
  sysctl:
    name: net.ipv4.conf.all.rp_filter
    value: '{{ sysctl_net_ipv4_conf_all_rp_filter_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88689-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_rp_filter

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.all.rp_filter%3D1%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_rp_filter.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.all.rp_filter static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_rp_filter_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_rp_filter:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_rp_filter:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_rp_filter:obj:1

net.ipv4.conf.all.rp_filter static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_rp_filter_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_rp_filter:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_rp_filter:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_rp_filter:obj:1

net.ipv4.conf.all.rp_filter static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_all_rp_filter_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_all_rp_filter:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.all.rp_filter[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.all.rp_filter set to 1 or 2  oval:ssg-test_sysctl_net_ipv4_conf_all_rp_filter_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv4.conf.all.rp_filter0
Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_secure_redirects mediumCCE-87848-8

Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_secure_redirects
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_all_secure_redirects:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-87848-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cobit5APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
pcidssReq-1.4.3
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.6
pcidss41.4.3, 1.4
Description
To set the runtime status of the net.ipv4.conf.all.secure_redirects kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.all.secure_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.all.secure_redirects = 0
Rationale
Accepting "secure" ICMP redirects (from those gateways listed as default gateways) has few legitimate uses. It should be disabled unless it is absolutely required.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.all.secure_redirects from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.all.secure_redirects.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.all.secure_redirects" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_conf_all_secure_redirects_value='0'


#
# Set runtime for net.ipv4.conf.all.secure_redirects
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.all.secure_redirects="$sysctl_net_ipv4_conf_all_secure_redirects_value"
fi

#
# If net.ipv4.conf.all.secure_redirects present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.conf.all.secure_redirects = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.all.secure_redirects")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_secure_redirects_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.all.secure_redirects\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.secure_redirects\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-87848-8"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87848-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_secure_redirects

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.all.secure_redirects.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87848-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_secure_redirects

- name: Comment out any occurrences of net.ipv4.conf.all.secure_redirects from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.all.secure_redirects
    replace: '#net.ipv4.conf.all.secure_redirects'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87848-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_secure_redirects
- name: XCCDF Value sysctl_net_ipv4_conf_all_secure_redirects_value # promote to variable
  set_fact:
    sysctl_net_ipv4_conf_all_secure_redirects_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv4.conf.all.secure_redirects is set
  sysctl:
    name: net.ipv4.conf.all.secure_redirects
    value: '{{ sysctl_net_ipv4_conf_all_secure_redirects_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87848-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_secure_redirects

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.all.secure_redirects%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_secure_redirects.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.all.secure_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_secure_redirects_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_secure_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_secure_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_secure_redirects:obj:1

net.ipv4.conf.all.secure_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_secure_redirects_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_secure_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_secure_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_secure_redirects:obj:1

net.ipv4.conf.all.secure_redirects static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_all_secure_redirects_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_all_secure_redirects:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.all.secure_redirects[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.all.secure_redirects set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_conf_all_secure_redirects_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv4.conf.all.secure_redirects1
Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_accept_redirects mediumCCE-86820-8

Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_accept_redirects
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_default_accept_redirects:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-86820-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis5.10.1.1
cobit5APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
pcidssReq-1.4.3
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.5
pcidss41.4.3, 1.4
Description
To set the runtime status of the net.ipv4.conf.default.accept_redirects kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.default.accept_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.default.accept_redirects = 0
Rationale
ICMP redirect messages are used by routers to inform hosts that a more direct route exists for a particular destination. These messages modify the host's route table and are unauthenticated. An illicit ICMP redirect message could result in a man-in-the-middle attack.
This feature of the IPv4 protocol has few legitimate uses. It should be disabled unless absolutely required.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.default.accept_redirects from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.default.accept_redirects.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.default.accept_redirects" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_conf_default_accept_redirects_value='0'


#
# Set runtime for net.ipv4.conf.default.accept_redirects
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.default.accept_redirects="$sysctl_net_ipv4_conf_default_accept_redirects_value"
fi

#
# If net.ipv4.conf.default.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.conf.default.accept_redirects = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.default.accept_redirects")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_accept_redirects_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.default.accept_redirects\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.accept_redirects\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-86820-8"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86820-8
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_accept_redirects

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.default.accept_redirects.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86820-8
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_accept_redirects

- name: Comment out any occurrences of net.ipv4.conf.default.accept_redirects from
    config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.default.accept_redirects
    replace: '#net.ipv4.conf.default.accept_redirects'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86820-8
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_accept_redirects
- name: XCCDF Value sysctl_net_ipv4_conf_default_accept_redirects_value # promote to variable
  set_fact:
    sysctl_net_ipv4_conf_default_accept_redirects_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv4.conf.default.accept_redirects is set
  sysctl:
    name: net.ipv4.conf.default.accept_redirects
    value: '{{ sysctl_net_ipv4_conf_default_accept_redirects_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86820-8
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_accept_redirects

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.default.accept_redirects%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_accept_redirects.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.default.accept_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_accept_redirects_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_accept_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_accept_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_accept_redirects:obj:1

net.ipv4.conf.default.accept_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_accept_redirects_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_accept_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_accept_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_accept_redirects:obj:1

net.ipv4.conf.default.accept_redirects static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_default_accept_redirects_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_default_accept_redirects:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.default.accept_redirects[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.default.accept_redirects set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_conf_default_accept_redirects_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv4.conf.default.accept_redirects1
Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces by Defaultxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_accept_source_route mediumCCE-88071-6

Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces by Default

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_accept_source_route
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_default_accept_source_route:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-88071-6

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis5.10.1.1
cobit5APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-7(a), CM-7(b), SC-5, SC-7(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.8
Description
To set the runtime status of the net.ipv4.conf.default.accept_source_route kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.default.accept_source_route=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.default.accept_source_route = 0
Rationale
Source-routed packets allow the source of the packet to suggest routers forward the packet along a different path than configured on the router, which can be used to bypass network security measures.
Accepting source-routed packets in the IPv4 protocol has few legitimate uses. It should be disabled unless it is absolutely required, such as when IPv4 forwarding is enabled and the system is legitimately functioning as a router.
OVAL test results details

net.ipv4.conf.default.accept_source_route static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_accept_source_route_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_accept_source_route:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_accept_source_route:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_accept_source_route:obj:1

net.ipv4.conf.default.accept_source_route static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_accept_source_route_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_accept_source_route:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_accept_source_route:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_accept_source_route:obj:1

net.ipv4.conf.default.accept_source_route static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_default_accept_source_route_static_pkg_correct:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/usr/lib/sysctl.d/50-default.confnet.ipv4.conf.default.accept_source_route = 0

kernel runtime parameter net.ipv4.conf.default.accept_source_route set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_conf_default_accept_source_route_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv4.conf.default.accept_source_route0
Enable Kernel Paremeter to Log Martian Packets on all IPv4 Interfaces by Defaultxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_log_martians unknownCCE-87672-2

Enable Kernel Paremeter to Log Martian Packets on all IPv4 Interfaces by Default

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_log_martians
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_default_log_martians:def:1
Time2025-07-22T08:31:44+00:00
Severityunknown
Identifiers:

CCE-87672-2

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.04, DSS03.05, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.06
cui3.1.20
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.11.2.6, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.2.1, A.6.2.2, A.9.1.2
nistCM-7(a), CM-7(b), SC-5(3)(a)
nist-csfDE.CM-1, PR.AC-3, PR.DS-4, PR.IP-1, PR.PT-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
cis3.3.9
Description
To set the runtime status of the net.ipv4.conf.default.log_martians kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.default.log_martians=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.default.log_martians = 1
Rationale
The presence of "martian" packets (which have impossible addresses) as well as spoofed packets, source-routed packets, and redirects could be a sign of nefarious network activity. Logging these packets enables this activity to be detected.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.default.log_martians from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.default.log_martians.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.default.log_martians" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_conf_default_log_martians_value='1'


#
# Set runtime for net.ipv4.conf.default.log_martians
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.default.log_martians="$sysctl_net_ipv4_conf_default_log_martians_value"
fi

#
# If net.ipv4.conf.default.log_martians present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.conf.default.log_martians = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.default.log_martians")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_log_martians_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.default.log_martians\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.log_martians\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-87672-2"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87672-2
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(3)(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_conf_default_log_martians
  - unknown_severity

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.default.log_martians.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87672-2
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(3)(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_conf_default_log_martians
  - unknown_severity

- name: Comment out any occurrences of net.ipv4.conf.default.log_martians from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.default.log_martians
    replace: '#net.ipv4.conf.default.log_martians'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87672-2
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(3)(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_conf_default_log_martians
  - unknown_severity
- name: XCCDF Value sysctl_net_ipv4_conf_default_log_martians_value # promote to variable
  set_fact:
    sysctl_net_ipv4_conf_default_log_martians_value: !!str 1
  tags:
    - always

- name: Ensure sysctl net.ipv4.conf.default.log_martians is set
  sysctl:
    name: net.ipv4.conf.default.log_martians
    value: '{{ sysctl_net_ipv4_conf_default_log_martians_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87672-2
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(3)(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_conf_default_log_martians
  - unknown_severity

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.default.log_martians%3D1%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_log_martians.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.default.log_martians static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_log_martians_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_log_martians:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_log_martians:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_log_martians:obj:1

net.ipv4.conf.default.log_martians static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_log_martians_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_log_martians:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_log_martians:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_log_martians:obj:1

net.ipv4.conf.default.log_martians static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_default_log_martians_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_default_log_martians:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.default.log_martians[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.default.log_martians set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_conf_default_log_martians_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv4.conf.default.log_martians0
Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces by Defaultxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_rp_filter mediumCCE-87424-8

Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces by Default

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_rp_filter
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_default_rp_filter:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-87424-8

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 2, 4, 6, 7, 8, 9
cobit5APO01.06, APO13.01, BAI04.04, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.7
Description
To set the runtime status of the net.ipv4.conf.default.rp_filter kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.default.rp_filter=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.default.rp_filter = 1
Rationale
Enabling reverse path filtering drops packets with source addresses that should not have been able to be received on the interface they were received on. It should not be used on systems which are routers for complicated networks, but is helpful for end hosts and routers serving small networks.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.default.rp_filter from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.default.rp_filter.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.default.rp_filter" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_conf_default_rp_filter_value='1'


#
# Set runtime for net.ipv4.conf.default.rp_filter
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.default.rp_filter="$sysctl_net_ipv4_conf_default_rp_filter_value"
fi

#
# If net.ipv4.conf.default.rp_filter present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.conf.default.rp_filter = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.default.rp_filter")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_rp_filter_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.default.rp_filter\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.rp_filter\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-87424-8"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87424-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_rp_filter

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.default.rp_filter.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87424-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_rp_filter

- name: Comment out any occurrences of net.ipv4.conf.default.rp_filter from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.default.rp_filter
    replace: '#net.ipv4.conf.default.rp_filter'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87424-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_rp_filter
- name: XCCDF Value sysctl_net_ipv4_conf_default_rp_filter_value # promote to variable
  set_fact:
    sysctl_net_ipv4_conf_default_rp_filter_value: !!str 1
  tags:
    - always

- name: Ensure sysctl net.ipv4.conf.default.rp_filter is set
  sysctl:
    name: net.ipv4.conf.default.rp_filter
    value: '{{ sysctl_net_ipv4_conf_default_rp_filter_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87424-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_rp_filter

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.default.rp_filter%3D1%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_rp_filter.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.default.rp_filter static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_rp_filter_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_rp_filter:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_rp_filter:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_rp_filter:obj:1

net.ipv4.conf.default.rp_filter static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_rp_filter_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_rp_filter:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_rp_filter:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_rp_filter:obj:1

net.ipv4.conf.default.rp_filter static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_default_rp_filter_static_pkg_correct:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
true/usr/lib/sysctl.d/50-redhat.confnet.ipv4.conf.default.rp_filter = 1
false/usr/lib/sysctl.d/50-default.confnet.ipv4.conf.default.rp_filter = 2

kernel runtime parameter net.ipv4.conf.default.rp_filter set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_conf_default_rp_filter_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv4.conf.default.rp_filter1
Configure Kernel Parameter for Accepting Secure Redirects By Defaultxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_secure_redirects mediumCCE-87878-5

Configure Kernel Parameter for Accepting Secure Redirects By Default

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_secure_redirects
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_default_secure_redirects:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-87878-5

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cobit5APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-7(a), CM-7(b), SC-5, SC-7(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.6
Description
To set the runtime status of the net.ipv4.conf.default.secure_redirects kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.default.secure_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.default.secure_redirects = 0
Rationale
Accepting "secure" ICMP redirects (from those gateways listed as default gateways) has few legitimate uses. It should be disabled unless it is absolutely required.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.default.secure_redirects from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.default.secure_redirects.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.default.secure_redirects" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_conf_default_secure_redirects_value='0'


#
# Set runtime for net.ipv4.conf.default.secure_redirects
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.default.secure_redirects="$sysctl_net_ipv4_conf_default_secure_redirects_value"
fi

#
# If net.ipv4.conf.default.secure_redirects present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.conf.default.secure_redirects = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.default.secure_redirects")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_secure_redirects_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.default.secure_redirects\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.secure_redirects\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-87878-5"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87878-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_secure_redirects

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.default.secure_redirects.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87878-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_secure_redirects

- name: Comment out any occurrences of net.ipv4.conf.default.secure_redirects from
    config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.default.secure_redirects
    replace: '#net.ipv4.conf.default.secure_redirects'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87878-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_secure_redirects
- name: XCCDF Value sysctl_net_ipv4_conf_default_secure_redirects_value # promote to variable
  set_fact:
    sysctl_net_ipv4_conf_default_secure_redirects_value: !!str 0
  tags:
    - always

- name: Ensure sysctl net.ipv4.conf.default.secure_redirects is set
  sysctl:
    name: net.ipv4.conf.default.secure_redirects
    value: '{{ sysctl_net_ipv4_conf_default_secure_redirects_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87878-5
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_secure_redirects

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.default.secure_redirects%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_secure_redirects.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.default.secure_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_secure_redirects_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_secure_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_secure_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_secure_redirects:obj:1

net.ipv4.conf.default.secure_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_secure_redirects_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_secure_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_secure_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_secure_redirects:obj:1

net.ipv4.conf.default.secure_redirects static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_default_secure_redirects_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_default_secure_redirects:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.default.secure_redirects[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.default.secure_redirects set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_conf_default_secure_redirects_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv4.conf.default.secure_redirects1
Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_icmp_echo_ignore_broadcasts mediumCCE-86918-0

Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_icmp_echo_ignore_broadcasts
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_icmp_echo_ignore_broadcasts:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-86918-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis5.10.1.1
cobit5APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-7(a), CM-7(b), SC-5
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
pcidssReq-1.4.3
os-srgSRG-OS-000480-GPOS-00227
cis3.3.4
pcidss41.4.2, 1.4
Description
To set the runtime status of the net.ipv4.icmp_echo_ignore_broadcasts kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.icmp_echo_ignore_broadcasts = 1
Rationale
Responding to broadcast (ICMP) echoes facilitates network mapping and provides a vector for amplification attacks.
Ignoring ICMP echo requests (pings) sent to broadcast or multicast addresses makes the system slightly more difficult to enumerate on the network.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.icmp_echo_ignore_broadcasts from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.icmp_echo_ignore_broadcasts.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.icmp_echo_ignore_broadcasts" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value='1'


#
# Set runtime for net.ipv4.icmp_echo_ignore_broadcasts
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.icmp_echo_ignore_broadcasts="$sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value"
fi

#
# If net.ipv4.icmp_echo_ignore_broadcasts present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.icmp_echo_ignore_broadcasts = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.icmp_echo_ignore_broadcasts")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.icmp_echo_ignore_broadcasts\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.icmp_echo_ignore_broadcasts\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-86918-0"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86918-0
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_icmp_echo_ignore_broadcasts

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.icmp_echo_ignore_broadcasts.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86918-0
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_icmp_echo_ignore_broadcasts

- name: Comment out any occurrences of net.ipv4.icmp_echo_ignore_broadcasts from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.icmp_echo_ignore_broadcasts
    replace: '#net.ipv4.icmp_echo_ignore_broadcasts'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86918-0
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_icmp_echo_ignore_broadcasts
- name: XCCDF Value sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value # promote to variable
  set_fact:
    sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value: !!str 1
  tags:
    - always

- name: Ensure sysctl net.ipv4.icmp_echo_ignore_broadcasts is set
  sysctl:
    name: net.ipv4.icmp_echo_ignore_broadcasts
    value: '{{ sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86918-0
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_icmp_echo_ignore_broadcasts

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.icmp_echo_ignore_broadcasts%3D1%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_icmp_echo_ignore_broadcasts.conf
        overwrite: true
OVAL test results details

net.ipv4.icmp_echo_ignore_broadcasts static configuration  oval:ssg-test_sysctl_net_ipv4_icmp_echo_ignore_broadcasts_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_icmp_echo_ignore_broadcasts:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_icmp_echo_ignore_broadcasts:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_icmp_echo_ignore_broadcasts:obj:1

net.ipv4.icmp_echo_ignore_broadcasts static configuration  oval:ssg-test_sysctl_net_ipv4_icmp_echo_ignore_broadcasts_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_icmp_echo_ignore_broadcasts:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_icmp_echo_ignore_broadcasts:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_icmp_echo_ignore_broadcasts:obj:1

net.ipv4.icmp_echo_ignore_broadcasts static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_icmp_echo_ignore_broadcasts_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_icmp_echo_ignore_broadcasts:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.icmp_echo_ignore_broadcasts[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.icmp_echo_ignore_broadcasts set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_icmp_echo_ignore_broadcasts_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv4.icmp_echo_ignore_broadcasts1
Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_icmp_ignore_bogus_error_responses unknownCCE-87841-3

Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_icmp_ignore_bogus_error_responses
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_icmp_ignore_bogus_error_responses:def:1
Time2025-07-22T08:31:44+00:00
Severityunknown
Identifiers:

CCE-87841-3

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS05.02, DSS05.05, DSS05.07, DSS06.06
cui3.1.20
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.9.1.2
nerc-cipCIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-7(a), CM-7(b), SC-5
nist-csfDE.CM-1, PR.DS-4, PR.IP-1, PR.PT-3
pcidssReq-1.4.3
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.3
pcidss41.4.2, 1.4
Description
To set the runtime status of the net.ipv4.icmp_ignore_bogus_error_responses kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.icmp_ignore_bogus_error_responses=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.icmp_ignore_bogus_error_responses = 1
Rationale
Ignoring bogus ICMP error responses reduces log size, although some activity would not be logged.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.icmp_ignore_bogus_error_responses from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.icmp_ignore_bogus_error_responses.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.icmp_ignore_bogus_error_responses" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value='1'


#
# Set runtime for net.ipv4.icmp_ignore_bogus_error_responses
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.icmp_ignore_bogus_error_responses="$sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value"
fi

#
# If net.ipv4.icmp_ignore_bogus_error_responses present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.icmp_ignore_bogus_error_responses = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.icmp_ignore_bogus_error_responses")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.icmp_ignore_bogus_error_responses\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.icmp_ignore_bogus_error_responses\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-87841-3"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87841-3
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
  - unknown_severity

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.icmp_ignore_bogus_error_responses.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87841-3
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
  - unknown_severity

- name: Comment out any occurrences of net.ipv4.icmp_ignore_bogus_error_responses
    from config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.icmp_ignore_bogus_error_responses
    replace: '#net.ipv4.icmp_ignore_bogus_error_responses'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87841-3
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
  - unknown_severity
- name: XCCDF Value sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value # promote to variable
  set_fact:
    sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value: !!str 1
  tags:
    - always

- name: Ensure sysctl net.ipv4.icmp_ignore_bogus_error_responses is set
  sysctl:
    name: net.ipv4.icmp_ignore_bogus_error_responses
    value: '{{ sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87841-3
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - PCI-DSS-Req-1.4.3
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - low_complexity
  - medium_disruption
  - reboot_required
  - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
  - unknown_severity

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.icmp_ignore_bogus_error_responses%3D1%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_icmp_ignore_bogus_error_responses.conf
        overwrite: true
OVAL test results details

net.ipv4.icmp_ignore_bogus_error_responses static configuration  oval:ssg-test_sysctl_net_ipv4_icmp_ignore_bogus_error_responses_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_icmp_ignore_bogus_error_responses:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_icmp_ignore_bogus_error_responses:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_icmp_ignore_bogus_error_responses:obj:1

net.ipv4.icmp_ignore_bogus_error_responses static configuration  oval:ssg-test_sysctl_net_ipv4_icmp_ignore_bogus_error_responses_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_icmp_ignore_bogus_error_responses:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_icmp_ignore_bogus_error_responses:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_icmp_ignore_bogus_error_responses:obj:1

net.ipv4.icmp_ignore_bogus_error_responses static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_icmp_ignore_bogus_error_responses_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_icmp_ignore_bogus_error_responses:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.icmp_ignore_bogus_error_responses[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.icmp_ignore_bogus_error_responses set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_icmp_ignore_bogus_error_responses_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv4.icmp_ignore_bogus_error_responses1
Enable Kernel Parameter to Use TCP Syncookies on Network Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_tcp_syncookies mediumCCE-88084-9

Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_tcp_syncookies
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_tcp_syncookies:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-88084-9

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 2, 4, 6, 7, 8, 9
cjis5.10.1.1
cobit5APO01.06, APO13.01, BAI04.04, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-7(a), CM-7(b), SC-5(1), SC-5(2), SC-5(3)(a), CM-6(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.PT-4
pcidssReq-1.4.1
os-srgSRG-OS-000480-GPOS-00227, SRG-OS-000420-GPOS-00186, SRG-OS-000142-GPOS-00071
anssiR12
cis3.3.10
pcidss41.4.3, 1.4
Description
To set the runtime status of the net.ipv4.tcp_syncookies kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.tcp_syncookies=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.tcp_syncookies = 1
Rationale
A TCP SYN flood attack can cause a denial of service by filling a system's TCP connection table with connections in the SYN_RCVD state. Syncookies can be used to track a connection when a subsequent ACK is received, verifying the initiator is attempting a valid connection and is not a flood source. This feature is activated when a flood condition is detected, and enables the system to continue servicing valid connection requests.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.tcp_syncookies from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.tcp_syncookies.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.tcp_syncookies" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"

sysctl_net_ipv4_tcp_syncookies_value='1'


#
# Set runtime for net.ipv4.tcp_syncookies
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.tcp_syncookies="$sysctl_net_ipv4_tcp_syncookies_value"
fi

#
# If net.ipv4.tcp_syncookies present in /etc/sysctl.conf, change value to appropriate value
#	else, add "net.ipv4.tcp_syncookies = value" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.tcp_syncookies")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_tcp_syncookies_value"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.tcp_syncookies\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.tcp_syncookies\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-88084-9"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88084-9
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(1)
  - NIST-800-53-SC-5(2)
  - NIST-800-53-SC-5(3)(a)
  - PCI-DSS-Req-1.4.1
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_tcp_syncookies

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.tcp_syncookies.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88084-9
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(1)
  - NIST-800-53-SC-5(2)
  - NIST-800-53-SC-5(3)(a)
  - PCI-DSS-Req-1.4.1
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_tcp_syncookies

- name: Comment out any occurrences of net.ipv4.tcp_syncookies from config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.tcp_syncookies
    replace: '#net.ipv4.tcp_syncookies'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88084-9
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(1)
  - NIST-800-53-SC-5(2)
  - NIST-800-53-SC-5(3)(a)
  - PCI-DSS-Req-1.4.1
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_tcp_syncookies
- name: XCCDF Value sysctl_net_ipv4_tcp_syncookies_value # promote to variable
  set_fact:
    sysctl_net_ipv4_tcp_syncookies_value: !!str 1
  tags:
    - always

- name: Ensure sysctl net.ipv4.tcp_syncookies is set
  sysctl:
    name: net.ipv4.tcp_syncookies
    value: '{{ sysctl_net_ipv4_tcp_syncookies_value }}'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88084-9
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5(1)
  - NIST-800-53-SC-5(2)
  - NIST-800-53-SC-5(3)(a)
  - PCI-DSS-Req-1.4.1
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_tcp_syncookies

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.tcp_syncookies%3D1%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_tcp_syncookies.conf
        overwrite: true
OVAL test results details

net.ipv4.tcp_syncookies static configuration  oval:ssg-test_sysctl_net_ipv4_tcp_syncookies_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_tcp_syncookies:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_tcp_syncookies:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_tcp_syncookies:obj:1

net.ipv4.tcp_syncookies static configuration  oval:ssg-test_sysctl_net_ipv4_tcp_syncookies_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_tcp_syncookies:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_tcp_syncookies:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_tcp_syncookies:obj:1

net.ipv4.tcp_syncookies static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_tcp_syncookies_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_tcp_syncookies:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.tcp_syncookies[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.tcp_syncookies set to the appropriate value  oval:ssg-test_sysctl_net_ipv4_tcp_syncookies_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv4.tcp_syncookies1
Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_send_redirects mediumCCE-88360-3

Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_send_redirects
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_all_send_redirects:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-88360-3

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis5.10.1.1
cobit5APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-7(a), CM-7(b), SC-5, CM-6(a), SC-7(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.2
pcidss41.4.5, 1.4
Description
To set the runtime status of the net.ipv4.conf.all.send_redirects kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.all.send_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.all.send_redirects = 0
Rationale
ICMP redirect messages are used by routers to inform hosts that a more direct route exists for a particular destination. These messages contain information from the system's route table possibly revealing portions of the network topology.
The ability to send ICMP redirects is only appropriate for systems acting as routers.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.all.send_redirects from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.all.send_redirects.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.all.send_redirects" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"


#
# Set runtime for net.ipv4.conf.all.send_redirects
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.all.send_redirects="0"
fi

#
# If net.ipv4.conf.all.send_redirects present in /etc/sysctl.conf, change value to "0"
#	else, add "net.ipv4.conf.all.send_redirects = 0" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.all.send_redirects")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "0"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.all.send_redirects\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.send_redirects\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-88360-3"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88360-3
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.5
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_send_redirects

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.all.send_redirects.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88360-3
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.5
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_send_redirects

- name: Comment out any occurrences of net.ipv4.conf.all.send_redirects from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.all.send_redirects
    replace: '#net.ipv4.conf.all.send_redirects'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88360-3
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.5
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_send_redirects

- name: Ensure sysctl net.ipv4.conf.all.send_redirects is set to 0
  sysctl:
    name: net.ipv4.conf.all.send_redirects
    value: '0'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88360-3
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.5
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_all_send_redirects

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.all.send_redirects%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_send_redirects.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.all.send_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_send_redirects_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_send_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_send_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_send_redirects:obj:1

net.ipv4.conf.all.send_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_all_send_redirects_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_all_send_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_all_send_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_all_send_redirects:obj:1

net.ipv4.conf.all.send_redirects static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_all_send_redirects_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_all_send_redirects:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.all.send_redirects[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.all.send_redirects set to 0  oval:ssg-test_sysctl_net_ipv4_conf_all_send_redirects_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv4.conf.all.send_redirects1
Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces by Defaultxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_send_redirects mediumCCE-89177-0

Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces by Default

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_send_redirects
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_conf_default_send_redirects:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-89177-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis5.10.1.1
cobit5APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui3.1.20
isa-62443-20094.2.3.4, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-7(a), CM-7(b), SC-5, CM-6(a), SC-7(a)
nist-csfDE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.2
pcidss41.4.5, 1.4
Description
To set the runtime status of the net.ipv4.conf.default.send_redirects kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.conf.default.send_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.conf.default.send_redirects = 0
Rationale
ICMP redirect messages are used by routers to inform hosts that a more direct route exists for a particular destination. These messages contain information from the system's route table possibly revealing portions of the network topology.
The ability to send ICMP redirects is only appropriate for systems acting as routers.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.conf.default.send_redirects from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.conf.default.send_redirects.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.conf.default.send_redirects" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"


#
# Set runtime for net.ipv4.conf.default.send_redirects
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.conf.default.send_redirects="0"
fi

#
# If net.ipv4.conf.default.send_redirects present in /etc/sysctl.conf, change value to "0"
#	else, add "net.ipv4.conf.default.send_redirects = 0" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.conf.default.send_redirects")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "0"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.conf.default.send_redirects\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.send_redirects\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-89177-0"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89177-0
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.5
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_send_redirects

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.conf.default.send_redirects.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89177-0
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.5
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_send_redirects

- name: Comment out any occurrences of net.ipv4.conf.default.send_redirects from config
    files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.conf.default.send_redirects
    replace: '#net.ipv4.conf.default.send_redirects'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89177-0
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.5
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_send_redirects

- name: Ensure sysctl net.ipv4.conf.default.send_redirects is set to 0
  sysctl:
    name: net.ipv4.conf.default.send_redirects
    value: '0'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89177-0
  - CJIS-5.10.1.1
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.5
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_conf_default_send_redirects

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,net.ipv4.conf.default.send_redirects%3D0%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_send_redirects.conf
        overwrite: true
OVAL test results details

net.ipv4.conf.default.send_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_send_redirects_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_send_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_send_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_send_redirects:obj:1

net.ipv4.conf.default.send_redirects static configuration  oval:ssg-test_sysctl_net_ipv4_conf_default_send_redirects_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_conf_default_send_redirects:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_conf_default_send_redirects:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_conf_default_send_redirects:obj:1

net.ipv4.conf.default.send_redirects static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_conf_default_send_redirects_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_conf_default_send_redirects:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.conf.default.send_redirects[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.conf.default.send_redirects set to 0  oval:ssg-test_sysctl_net_ipv4_conf_default_send_redirects_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsenet.ipv4.conf.default.send_redirects1
Disable Kernel Parameter for IP Forwarding on IPv4 Interfacesxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_ip_forward mediumCCE-87377-8

Disable Kernel Parameter for IP Forwarding on IPv4 Interfaces

Rule IDxccdf_org.ssgproject.content_rule_sysctl_net_ipv4_ip_forward
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_net_ipv4_ip_forward:def:1
Time2025-07-22T08:31:44+00:00
Severitymedium
Identifiers:

CCE-87377-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS05.02, DSS05.05, DSS05.07, DSS06.06
cui3.1.20
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.9.1.2
nerc-cipCIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-7(a), CM-7(b), SC-5, CM-6(a), SC-7(a)
nist-csfDE.CM-1, PR.DS-4, PR.IP-1, PR.PT-3, PR.PT-4
pcidssReq-1.3.1, Req-1.3.2
os-srgSRG-OS-000480-GPOS-00227
anssiR12
cis3.3.1
pcidss41.4.3, 1.4
Description
To set the runtime status of the net.ipv4.ip_forward kernel parameter, run the following command:
$ sudo sysctl -w net.ipv4.ip_forward=0
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
net.ipv4.ip_forward = 0
Rationale
Routing protocol daemons are typically used on routers to exchange network topology information with other routers. If this capability is used when not required, system network information may be unnecessarily transmitted across the network.
Warnings
warning  Certain technologies such as virtual machines, containers, etc. rely on IPv4 forwarding to enable and use networking. Disabling IPv4 forwarding would cause those technologies to stop working. Therefore, this rule should not be used in profiles or benchmarks that target usage of IPv4 forwarding.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of net.ipv4.ip_forward from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.ip_forward.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "net.ipv4.ip_forward" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"


#
# Set runtime for net.ipv4.ip_forward
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w net.ipv4.ip_forward="0"
fi

#
# If net.ipv4.ip_forward present in /etc/sysctl.conf, change value to "0"
#	else, add "net.ipv4.ip_forward = 0" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.ip_forward")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "0"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.ip_forward\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.ip_forward\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-87377-8"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87377-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.3.1
  - PCI-DSS-Req-1.3.2
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_ip_forward

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*net.ipv4.ip_forward.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87377-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.3.1
  - PCI-DSS-Req-1.3.2
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_ip_forward

- name: Comment out any occurrences of net.ipv4.ip_forward from config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*net.ipv4.ip_forward
    replace: '#net.ipv4.ip_forward'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87377-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.3.1
  - PCI-DSS-Req-1.3.2
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_ip_forward

- name: Ensure sysctl net.ipv4.ip_forward is set to 0
  sysctl:
    name: net.ipv4.ip_forward
    value: '0'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87377-8
  - NIST-800-171-3.1.20
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-SC-5
  - NIST-800-53-SC-7(a)
  - PCI-DSS-Req-1.3.1
  - PCI-DSS-Req-1.3.2
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.3
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_net_ipv4_ip_forward
OVAL test results details

net.ipv4.ip_forward static configuration  oval:ssg-test_sysctl_net_ipv4_ip_forward_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_ip_forward:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_ip_forward:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_ip_forward:obj:1

net.ipv4.ip_forward static configuration  oval:ssg-test_sysctl_net_ipv4_ip_forward_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_net_ipv4_ip_forward:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_net_ipv4_ip_forward:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_net_ipv4_ip_forward:obj:1

net.ipv4.ip_forward static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_net_ipv4_ip_forward_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_net_ipv4_ip_forward:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*net.ipv4.ip_forward[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter net.ipv4.ip_forward set to 0  oval:ssg-test_sysctl_net_ipv4_ip_forward_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truenet.ipv4.ip_forward0
Install nftables Packagexccdf_org.ssgproject.content_rule_package_nftables_installed mediumCCE-87358-8

Install nftables Package

Rule IDxccdf_org.ssgproject.content_rule_package_nftables_installed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_nftables_installed:def:1
Time2025-07-22T08:31:45+00:00
Severitymedium
Identifiers:

CCE-87358-8

References:
cis4.1.1
pcidss41.2.1, 1.2
Description
nftables provides a new in-kernel packet classification framework that is based on a network-specific Virtual Machine (VM) and a new nft userspace command line tool. nftables reuses the existing Netfilter subsystems such as the existing hook infrastructure, the connection tracking system, NAT, userspace queuing and logging subsystem. The nftables package can be installed with the following command:
$ sudo dnf install nftables
Rationale
nftables is a subsystem of the Linux kernel that can protect against threats originating from within a corporate network to include malicious mobile code and poorly configured software on a host.
OVAL test results details

package nftables is installed  oval:ssg-test_package_nftables_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatednftablesx86_6414.el10_01.1.11:1.1.1-4.el10_0199e2f91fd431d51nftables-1:1.1.1-4.el10_0.x86_64
Verify nftables Service is Disabledxccdf_org.ssgproject.content_rule_service_nftables_disabled mediumCCE-88523-6

Verify nftables Service is Disabled

Rule IDxccdf_org.ssgproject.content_rule_service_nftables_disabled
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:45+00:00
Severitymedium
Identifiers:

CCE-88523-6

References:
cis4.1.2
pcidss41.2.1, 1.2
Description
nftables is a subsystem of the Linux kernel providing filtering and classification of network packets/datagrams/frames and is the successor to iptables. The nftables service can be disabled with the following command:
systemctl disable nftables
Rationale
Running both firewalld and nftables may lead to conflict. nftables is actually one of the backends for firewalld management tools.
Disable SCTP Supportxccdf_org.ssgproject.content_rule_kernel_module_sctp_disabled mediumCCE-90489-6

Disable SCTP Support

Rule IDxccdf_org.ssgproject.content_rule_kernel_module_sctp_disabled
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-kernel_module_sctp_disabled:def:1
Time2025-07-22T08:31:45+00:00
Severitymedium
Identifiers:

CCE-90489-6

References:
cis-csc11, 14, 3, 9
cjis5.10.1
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui3.4.6
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
osppFMT_SMF_EXT.1
pcidssReq-1.4.2
os-srgSRG-OS-000095-GPOS-00049, SRG-OS-000480-GPOS-00227
cis3.2.4
pcidss41.4.2, 1.4
Description
The Stream Control Transmission Protocol (SCTP) is a transport layer protocol, designed to support the idea of message-oriented communication, with several streams of messages within one connection. To configure the system to prevent the sctp kernel module from being loaded, add the following line to the file /etc/modprobe.d/sctp.conf:
install sctp /bin/false
Rationale
Disabling SCTP protects the system against exploitation of any flaws in its implementation.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if LC_ALL=C grep -q -m 1 "^install sctp" /etc/modprobe.d/sctp.conf ; then
	
	sed -i 's#^install sctp.*#install sctp /bin/false#g' /etc/modprobe.d/sctp.conf
else
	echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/sctp.conf
	echo "install sctp /bin/false" >> /etc/modprobe.d/sctp.conf
fi

if ! LC_ALL=C grep -q -m 1 "^blacklist sctp$" /etc/modprobe.d/sctp.conf ; then
	echo "blacklist sctp" >> /etc/modprobe.d/sctp.conf
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90489-6
  - CJIS-5.10.1
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-1.4.2
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - kernel_module_sctp_disabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required

- name: Ensure kernel module 'sctp' is disabled
  lineinfile:
    create: true
    dest: /etc/modprobe.d/sctp.conf
    regexp: install\s+sctp
    line: install sctp /bin/false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90489-6
  - CJIS-5.10.1
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-1.4.2
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - kernel_module_sctp_disabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required

- name: Ensure kernel module 'sctp' is blacklisted
  lineinfile:
    create: true
    dest: /etc/modprobe.d/sctp.conf
    regexp: ^blacklist sctp$
    line: blacklist sctp
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90489-6
  - CJIS-5.10.1
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-1.4.2
  - PCI-DSSv4-1.4
  - PCI-DSSv4-1.4.2
  - disable_strategy
  - kernel_module_sctp_disabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,install%20sctp%20/bin/false%0Ablacklist%20sctp%0A
        mode: 0644
        path: /etc/modprobe.d/sctp.conf
        overwrite: true
OVAL test results details

kernel module sctp blacklisted  oval:ssg-test_kernmod_sctp_blacklisted:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_sctp_blacklisted:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^blacklist\s+sctp$1

kernel module sctp disabled  oval:ssg-test_kernmod_sctp_disabled:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_sctp_disabled:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^\s*install\s+sctp\s+(/bin/false|/bin/true)$1
Disable TIPC Supportxccdf_org.ssgproject.content_rule_kernel_module_tipc_disabled lowCCE-86569-1

Disable TIPC Support

Rule IDxccdf_org.ssgproject.content_rule_kernel_module_tipc_disabled
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-kernel_module_tipc_disabled:def:1
Time2025-07-22T08:31:45+00:00
Severitylow
Identifiers:

CCE-86569-1

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
osppFMT_SMF_EXT.1
os-srgSRG-OS-000095-GPOS-00049
cis3.2.2
Description
The Transparent Inter-Process Communication (TIPC) protocol is designed to provide communications between nodes in a cluster. To configure the system to prevent the tipc kernel module from being loaded, add the following line to the file /etc/modprobe.d/tipc.conf:
install tipc /bin/false
Rationale
Disabling TIPC protects the system against exploitation of any flaws in its implementation.
Warnings
warning  This configuration baseline was created to deploy the base operating system for general purpose workloads. When the operating system is configured for certain purposes, such as a node in High Performance Computing cluster, it is expected that the tipc kernel module will be loaded.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if LC_ALL=C grep -q -m 1 "^install tipc" /etc/modprobe.d/tipc.conf ; then
	
	sed -i 's#^install tipc.*#install tipc /bin/false#g' /etc/modprobe.d/tipc.conf
else
	echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/tipc.conf
	echo "install tipc /bin/false" >> /etc/modprobe.d/tipc.conf
fi

if ! LC_ALL=C grep -q -m 1 "^blacklist tipc$" /etc/modprobe.d/tipc.conf ; then
	echo "blacklist tipc" >> /etc/modprobe.d/tipc.conf
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86569-1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - kernel_module_tipc_disabled
  - low_complexity
  - low_severity
  - medium_disruption
  - reboot_required

- name: Ensure kernel module 'tipc' is disabled
  lineinfile:
    create: true
    dest: /etc/modprobe.d/tipc.conf
    regexp: install\s+tipc
    line: install tipc /bin/false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86569-1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - kernel_module_tipc_disabled
  - low_complexity
  - low_severity
  - medium_disruption
  - reboot_required

- name: Ensure kernel module 'tipc' is blacklisted
  lineinfile:
    create: true
    dest: /etc/modprobe.d/tipc.conf
    regexp: ^blacklist tipc$
    line: blacklist tipc
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86569-1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - kernel_module_tipc_disabled
  - low_complexity
  - low_severity
  - medium_disruption
  - reboot_required

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,install%20tipc%20/bin/false%0Ablacklist%20tipc%0A
        mode: 0644
        path: /etc/modprobe.d/tipc.conf
        overwrite: true
OVAL test results details

kernel module tipc blacklisted  oval:ssg-test_kernmod_tipc_blacklisted:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_tipc_blacklisted:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^blacklist\s+tipc$1

kernel module tipc disabled  oval:ssg-test_kernmod_tipc_disabled:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_tipc_disabled:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^\s*install\s+tipc\s+(/bin/false|/bin/true)$1
Disable Bluetooth Servicexccdf_org.ssgproject.content_rule_service_bluetooth_disabled mediumCCE-90381-5

Disable Bluetooth Service

Rule IDxccdf_org.ssgproject.content_rule_service_bluetooth_disabled
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-service_bluetooth_disabled:def:1
Time2025-07-22T08:31:46+00:00
Severitymedium
Identifiers:

CCE-90381-5

References:
cis-csc11, 12, 14, 15, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
cui3.1.16
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2
nistAC-18(a), AC-18(3), CM-7(a), CM-7(b), CM-6(a), MP-7
nist-csfPR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
cis3.1.3
Description
The bluetooth service can be disabled with the following command:
$ sudo systemctl mask --now bluetooth.service
$ sudo service bluetooth stop
Rationale
Disabling the bluetooth service prevents the system from attempting connections to Bluetooth devices, which entails some security risk. Nevertheless, variation in this risk decision may be expected due to the utility of Bluetooth connectivity and its limited range.
OVAL test results details

package bluez is removed  oval:ssg-service_bluetooth_disabled_test_service_bluetooth_package_bluez_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_bluetooth_disabled_test_service_bluetooth_package_bluez_removed:obj:1 of type rpminfo_object
Name
bluez

Test that the bluetooth service is not running  oval:ssg-test_service_not_running_service_bluetooth_disabled_bluetooth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_not_running_service_bluetooth_disabled_bluetooth:obj:1 of type systemdunitproperty_object
UnitProperty
^bluetooth\.(service|socket)$ActiveState

Test that the property LoadState from the service bluetooth is masked  oval:ssg-test_service_loadstate_is_masked_service_bluetooth_disabled_bluetooth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_loadstate_is_masked_service_bluetooth_disabled_bluetooth:obj:1 of type systemdunitproperty_object
UnitProperty
^bluetooth\.(service|socket)$LoadState
Deactivate Wireless Network Interfacesxccdf_org.ssgproject.content_rule_wireless_disable_interfaces mediumCCE-88576-4

Deactivate Wireless Network Interfaces

Rule IDxccdf_org.ssgproject.content_rule_wireless_disable_interfaces
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:46+00:00
Severitymedium
Identifiers:

CCE-88576-4

References:
cis-csc11, 12, 14, 15, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
cui3.1.16
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
ism1315, 1319
iso27001-2013A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2
nistAC-18(a), AC-18(3), CM-7(a), CM-7(b), CM-6(a), MP-7
nist-csfPR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
pcidssReq-1.3.3
os-srgSRG-OS-000299-GPOS-00117, SRG-OS-000300-GPOS-00118, SRG-OS-000424-GPOS-00188, SRG-OS-000481-GPOS-00481
cis3.1.2
pcidss41.3.3, 1.3
Description
Deactivating wireless network interfaces should prevent normal usage of the wireless capability.

Configure the system to disable all wireless network interfaces with the following command:
$ sudo nmcli radio all off
Rationale
The use of wireless networking can introduce many different attack vectors into the organization's network. Common attack vectors such as malicious association and ad hoc networks will allow an attacker to spoof a wireless access point (AP), allowing validated systems to connect to the malicious AP and enabling the attacker to monitor and record network traffic. These malicious APs can also serve to create a man-in-the-middle attack or be used to create a denial of service to valid network resources.
Verify Permissions and Ownership of Old Passwords Filexccdf_org.ssgproject.content_rule_file_etc_security_opasswd mediumCCE-86980-0

Verify Permissions and Ownership of Old Passwords File

Rule IDxccdf_org.ssgproject.content_rule_file_etc_security_opasswd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_etc_security_opasswd:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86980-0

References:
os-srgSRG-OS-000077-GPOS-00045
cis7.1.10
Description
To properly set the owner of /etc/security/opasswd, run the command:
$ sudo chown root /etc/security/opasswd 
To properly set the group owner of /etc/security/opasswd, run the command:
$ sudo chgrp root /etc/security/opasswd
To properly set the permissions of /etc/security/opasswd, run the command:
$ sudo chmod 0600 /etc/security/opasswd
Rationale
The /etc/security/opasswd file stores old passwords to prevent password reuse. Protection of this file is critical for system security.
OVAL test results details

/etc/security/opasswd is owned by root:root / 0600  oval:ssg-test_file_etc_security_opasswd:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
true/etc/security/opasswdregular000rw------- 
Verify Group Who Owns Backup group Filexccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_group mediumCCE-89477-4

Verify Group Who Owns Backup group File

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_group
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_backup_etc_group:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89477-4

References:
nistAC-6 (1)
pcidssReq-8.7
os-srgSRG-OS-000480-GPOS-00227
cis7.1.4
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/group-, run the command:
$ sudo chgrp root /etc/group-
Rationale
The /etc/group- file is a backup file of /etc/group, and as such, it contains information regarding groups that are configured on the system. Protection of this file is important for system security.
OVAL test results details

Testing group ownership of /etc/group-  oval:ssg-test_file_groupowner_backup_etc_group_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_backup_etc_group_0:obj:1 of type file_object
FilepathFilterFilter
/etc/group-oval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_backup_etc_group_0_0:ste:1
Verify Group Who Owns Backup gshadow Filexccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_gshadow mediumCCE-88453-6

Verify Group Who Owns Backup gshadow File

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_gshadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_backup_etc_gshadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88453-6

References:
nistAC-6 (1)
pcidssReq-8.7
os-srgSRG-OS-000480-GPOS-00227
cis7.1.8
Description
To properly set the group owner of /etc/gshadow-, run the command:
$ sudo chgrp root /etc/gshadow-
Rationale
The /etc/gshadow- file is a backup of /etc/gshadow, and as such, it contains group password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing group ownership of /etc/gshadow-  oval:ssg-test_file_groupowner_backup_etc_gshadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_backup_etc_gshadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/gshadow-oval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_backup_etc_gshadow_0_0:ste:1
Verify Group Who Owns Backup passwd Filexccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_passwd mediumCCE-89914-6

Verify Group Who Owns Backup passwd File

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_passwd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_backup_etc_passwd:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89914-6

References:
nistAC-6 (1)
pcidssReq-8.7
os-srgSRG-OS-000480-GPOS-00227
cis7.1.2
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/passwd-, run the command:
$ sudo chgrp root /etc/passwd-
Rationale
The /etc/passwd- file is a backup file of /etc/passwd, and as such, it contains information about the users that are configured on the system. Protection of this file is critical for system security.
OVAL test results details

Testing group ownership of /etc/passwd-  oval:ssg-test_file_groupowner_backup_etc_passwd_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_backup_etc_passwd_0:obj:1 of type file_object
FilepathFilterFilter
/etc/passwd-oval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_backup_etc_passwd_0_0:ste:1
Verify User Who Owns Backup shadow Filexccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_shadow mediumCCE-88235-7

Verify User Who Owns Backup shadow File

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_shadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_backup_etc_shadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88235-7

References:
pcidssReq-8.7
os-srgSRG-OS-000480-GPOS-00227
cis7.1.6
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/shadow-, run the command:
$ sudo chgrp root /etc/shadow-
Rationale
The /etc/shadow- file is a backup file of /etc/shadow, and as such, it contains the list of local system accounts and password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing group ownership of /etc/shadow-  oval:ssg-test_file_groupowner_backup_etc_shadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_backup_etc_shadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/shadow-oval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_backup_etc_shadow_0_0:ste:1
Verify Group Who Owns group Filexccdf_org.ssgproject.content_rule_file_groupowner_etc_group mediumCCE-90261-9

Verify Group Who Owns group File

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_etc_group
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_etc_group:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-90261-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.3
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/group, run the command:
$ sudo chgrp root /etc/group
Rationale
The /etc/group file contains information regarding groups that are configured on the system. Protection of this file is important for system security.
OVAL test results details

Testing group ownership of /etc/group  oval:ssg-test_file_groupowner_etc_group_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_etc_group_0:obj:1 of type file_object
FilepathFilterFilter
/etc/groupoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_etc_group_0_0:ste:1
Verify Group Who Owns gshadow Filexccdf_org.ssgproject.content_rule_file_groupowner_etc_gshadow mediumCCE-90043-1

Verify Group Who Owns gshadow File

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_etc_gshadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_etc_gshadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-90043-1

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.7
Description
To properly set the group owner of /etc/gshadow, run the command:
$ sudo chgrp root /etc/gshadow
Rationale
The /etc/gshadow file contains group password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing group ownership of /etc/gshadow  oval:ssg-test_file_groupowner_etc_gshadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_etc_gshadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/gshadowoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_etc_gshadow_0_0:ste:1
Verify Group Who Owns passwd Filexccdf_org.ssgproject.content_rule_file_groupowner_etc_passwd mediumCCE-89210-9

Verify Group Who Owns passwd File

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_etc_passwd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_etc_passwd:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89210-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.1
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/passwd, run the command:
$ sudo chgrp root /etc/passwd
Rationale
The /etc/passwd file contains information about the users that are configured on the system. Protection of this file is critical for system security.
OVAL test results details

Testing group ownership of /etc/passwd  oval:ssg-test_file_groupowner_etc_passwd_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_etc_passwd_0:obj:1 of type file_object
FilepathFilterFilter
/etc/passwdoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_etc_passwd_0_0:ste:1
Verify Group Who Owns shadow Filexccdf_org.ssgproject.content_rule_file_groupowner_etc_shadow mediumCCE-87579-9

Verify Group Who Owns shadow File

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_etc_shadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_etc_shadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87579-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.5
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/shadow, run the command:
$ sudo chgrp root /etc/shadow
Rationale
The /etc/shadow file stores password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing group ownership of /etc/shadow  oval:ssg-test_file_groupowner_etc_shadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_etc_shadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/shadowoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_etc_shadow_0_0:ste:1
Verify Group Who Owns /etc/shells Filexccdf_org.ssgproject.content_rule_file_groupowner_etc_shells mediumCCE-90020-9

Verify Group Who Owns /etc/shells File

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_etc_shells
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_etc_shells:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-90020-9

References:
nistAC-3, MP-2
anssiR50
cis7.1.9
Description
To properly set the group owner of /etc/shells, run the command:
$ sudo chgrp root /etc/shells
Rationale
The /etc/shells file contains the list of full pathnames to shells on the system. Since this file is used by many system programs this file should be protected.
OVAL test results details

Testing group ownership of /etc/shells  oval:ssg-test_file_groupowner_etc_shells_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_etc_shells_0:obj:1 of type file_object
FilepathFilterFilter
/etc/shellsoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_etc_shells_0_0:ste:1
Verify User Who Owns Backup group Filexccdf_org.ssgproject.content_rule_file_owner_backup_etc_group mediumCCE-89017-8

Verify User Who Owns Backup group File

Rule IDxccdf_org.ssgproject.content_rule_file_owner_backup_etc_group
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_backup_etc_group:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89017-8

References:
nistAC-6 (1)
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
cis7.1.4
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/group-, run the command:
$ sudo chown root /etc/group- 
Rationale
The /etc/group- file is a backup file of /etc/group, and as such, it contains information regarding groups that are configured on the system. Protection of this file is important for system security.
OVAL test results details

Testing user ownership of /etc/group-  oval:ssg-test_file_owner_backup_etc_group_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_backup_etc_group_0:obj:1 of type file_object
FilepathFilterFilter
/etc/group-oval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_backup_etc_group_0_0:ste:1
Verify User Who Owns Backup gshadow Filexccdf_org.ssgproject.content_rule_file_owner_backup_etc_gshadow mediumCCE-86957-8

Verify User Who Owns Backup gshadow File

Rule IDxccdf_org.ssgproject.content_rule_file_owner_backup_etc_gshadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_backup_etc_gshadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86957-8

References:
nistAC-6 (1)
pcidssReq-8.7
os-srgSRG-OS-000480-GPOS-00227
cis7.1.8
Description
To properly set the owner of /etc/gshadow-, run the command:
$ sudo chown root /etc/gshadow- 
Rationale
The /etc/gshadow- file is a backup of /etc/gshadow, and as such, it contains group password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing user ownership of /etc/gshadow-  oval:ssg-test_file_owner_backup_etc_gshadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_backup_etc_gshadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/gshadow-oval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_backup_etc_gshadow_0_0:ste:1
Verify User Who Owns Backup passwd Filexccdf_org.ssgproject.content_rule_file_owner_backup_etc_passwd mediumCCE-90377-3

Verify User Who Owns Backup passwd File

Rule IDxccdf_org.ssgproject.content_rule_file_owner_backup_etc_passwd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_backup_etc_passwd:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-90377-3

References:
nistAC-6 (1)
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
cis7.1.2
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/passwd-, run the command:
$ sudo chown root /etc/passwd- 
Rationale
The /etc/passwd- file is a backup file of /etc/passwd, and as such, it contains information about the users that are configured on the system. Protection of this file is critical for system security.
OVAL test results details

Testing user ownership of /etc/passwd-  oval:ssg-test_file_owner_backup_etc_passwd_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_backup_etc_passwd_0:obj:1 of type file_object
FilepathFilterFilter
/etc/passwd-oval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_backup_etc_passwd_0_0:ste:1
Verify Group Who Owns Backup shadow Filexccdf_org.ssgproject.content_rule_file_owner_backup_etc_shadow mediumCCE-87502-1

Verify Group Who Owns Backup shadow File

Rule IDxccdf_org.ssgproject.content_rule_file_owner_backup_etc_shadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_backup_etc_shadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87502-1

References:
nistAC-6 (1)
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
cis7.1.6
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/shadow-, run the command:
$ sudo chown root /etc/shadow- 
Rationale
The /etc/shadow- file is a backup file of /etc/shadow, and as such, it contains the list of local system accounts and password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing user ownership of /etc/shadow-  oval:ssg-test_file_owner_backup_etc_shadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_backup_etc_shadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/shadow-oval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_backup_etc_shadow_0_0:ste:1
Verify User Who Owns group Filexccdf_org.ssgproject.content_rule_file_owner_etc_group mediumCCE-86870-3

Verify User Who Owns group File

Rule IDxccdf_org.ssgproject.content_rule_file_owner_etc_group
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_etc_group:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86870-3

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.3
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/group, run the command:
$ sudo chown root /etc/group 
Rationale
The /etc/group file contains information regarding groups that are configured on the system. Protection of this file is important for system security.
OVAL test results details

Testing user ownership of /etc/group  oval:ssg-test_file_owner_etc_group_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_etc_group_0:obj:1 of type file_object
FilepathFilterFilter
/etc/groupoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_etc_group_0_0:ste:1
Verify User Who Owns gshadow Filexccdf_org.ssgproject.content_rule_file_owner_etc_gshadow mediumCCE-87701-9

Verify User Who Owns gshadow File

Rule IDxccdf_org.ssgproject.content_rule_file_owner_etc_gshadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_etc_gshadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87701-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.7
Description
To properly set the owner of /etc/gshadow, run the command:
$ sudo chown root /etc/gshadow 
Rationale
The /etc/gshadow file contains group password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing user ownership of /etc/gshadow  oval:ssg-test_file_owner_etc_gshadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_etc_gshadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/gshadowoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_etc_gshadow_0_0:ste:1
Verify User Who Owns passwd Filexccdf_org.ssgproject.content_rule_file_owner_etc_passwd mediumCCE-87827-2

Verify User Who Owns passwd File

Rule IDxccdf_org.ssgproject.content_rule_file_owner_etc_passwd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_etc_passwd:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87827-2

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.1
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/passwd, run the command:
$ sudo chown root /etc/passwd 
Rationale
The /etc/passwd file contains information about the users that are configured on the system. Protection of this file is critical for system security.
OVAL test results details

Testing user ownership of /etc/passwd  oval:ssg-test_file_owner_etc_passwd_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_etc_passwd_0:obj:1 of type file_object
FilepathFilterFilter
/etc/passwdoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_etc_passwd_0_0:ste:1
Verify User Who Owns shadow Filexccdf_org.ssgproject.content_rule_file_owner_etc_shadow mediumCCE-86857-0

Verify User Who Owns shadow File

Rule IDxccdf_org.ssgproject.content_rule_file_owner_etc_shadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_etc_shadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86857-0

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.5
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/shadow, run the command:
$ sudo chown root /etc/shadow 
Rationale
The /etc/shadow file contains the list of local system accounts and stores password hashes. Protection of this file is critical for system security. Failure to give ownership of this file to root provides the designated owner with access to sensitive information which could weaken the system security posture.
OVAL test results details

Testing user ownership of /etc/shadow  oval:ssg-test_file_owner_etc_shadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_etc_shadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/shadowoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_etc_shadow_0_0:ste:1
Verify Who Owns /etc/shells Filexccdf_org.ssgproject.content_rule_file_owner_etc_shells mediumCCE-89594-6

Verify Who Owns /etc/shells File

Rule IDxccdf_org.ssgproject.content_rule_file_owner_etc_shells
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_etc_shells:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89594-6

References:
nistAC-3, MP-2
anssiR50
cis7.1.9
Description
To properly set the owner of /etc/shells, run the command:
$ sudo chown root /etc/shells 
Rationale
The /etc/shells file contains the list of full pathnames to shells on the system. Since this file is used by many system programs this file should be protected.
OVAL test results details

Testing user ownership of /etc/shells  oval:ssg-test_file_owner_etc_shells_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_etc_shells_0:obj:1 of type file_object
FilepathFilterFilter
/etc/shellsoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_etc_shells_0_0:ste:1
Verify Permissions on Backup group Filexccdf_org.ssgproject.content_rule_file_permissions_backup_etc_group mediumCCE-86579-0

Verify Permissions on Backup group File

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_backup_etc_group
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_backup_etc_group:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86579-0

References:
nistAC-6 (1)
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
cis7.1.4
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/group-, run the command:
$ sudo chmod 0644 /etc/group-
Rationale
The /etc/group- file is a backup file of /etc/group, and as such, it contains information regarding groups that are configured on the system. Protection of this file is important for system security.
OVAL test results details

Testing mode of /etc/group-  oval:ssg-test_file_permissions_backup_etc_group_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_backup_etc_group_0:obj:1 of type file_object
FilepathFilterFilter
/etc/group-oval:ssg-exclude_symlinks__backup_etc_group:ste:1oval:ssg-state_file_permissions_backup_etc_group_0_mode_0644or_stricter_:ste:1
Verify Permissions on Backup gshadow Filexccdf_org.ssgproject.content_rule_file_permissions_backup_etc_gshadow mediumCCE-89056-6

Verify Permissions on Backup gshadow File

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_backup_etc_gshadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_backup_etc_gshadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89056-6

References:
nistAC-6 (1)
os-srgSRG-OS-000480-GPOS-00227
cis7.1.8
Description
To properly set the permissions of /etc/gshadow-, run the command:
$ sudo chmod 0000 /etc/gshadow-
Rationale
The /etc/gshadow- file is a backup of /etc/gshadow, and as such, it contains group password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing mode of /etc/gshadow-  oval:ssg-test_file_permissions_backup_etc_gshadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_backup_etc_gshadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/gshadow-oval:ssg-exclude_symlinks__backup_etc_gshadow:ste:1oval:ssg-state_file_permissions_backup_etc_gshadow_0_mode_0000or_stricter_:ste:1
Verify Permissions on Backup passwd Filexccdf_org.ssgproject.content_rule_file_permissions_backup_etc_passwd mediumCCE-86854-7

Verify Permissions on Backup passwd File

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_backup_etc_passwd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_backup_etc_passwd:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86854-7

References:
nistAC-6 (1)
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
cis7.1.2
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/passwd-, run the command:
$ sudo chmod 0644 /etc/passwd-
Rationale
The /etc/passwd- file is a backup file of /etc/passwd, and as such, it contains information about the users that are configured on the system. Protection of this file is critical for system security.
OVAL test results details

Testing mode of /etc/passwd-  oval:ssg-test_file_permissions_backup_etc_passwd_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_backup_etc_passwd_0:obj:1 of type file_object
FilepathFilterFilter
/etc/passwd-oval:ssg-exclude_symlinks__backup_etc_passwd:ste:1oval:ssg-state_file_permissions_backup_etc_passwd_0_mode_0644or_stricter_:ste:1
Verify Permissions on Backup shadow Filexccdf_org.ssgproject.content_rule_file_permissions_backup_etc_shadow mediumCCE-87423-0

Verify Permissions on Backup shadow File

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_backup_etc_shadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_backup_etc_shadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87423-0

References:
nistAC-6 (1)
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
cis7.1.6
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/shadow-, run the command:
$ sudo chmod 0000 /etc/shadow-
Rationale
The /etc/shadow- file is a backup file of /etc/shadow, and as such, it contains the list of local system accounts and password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing mode of /etc/shadow-  oval:ssg-test_file_permissions_backup_etc_shadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_backup_etc_shadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/shadow-oval:ssg-exclude_symlinks__backup_etc_shadow:ste:1oval:ssg-state_file_permissions_backup_etc_shadow_0_mode_0000or_stricter_:ste:1
Verify Permissions on group Filexccdf_org.ssgproject.content_rule_file_permissions_etc_group mediumCCE-88868-5

Verify Permissions on group File

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_etc_group
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_etc_group:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88868-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.3
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/group, run the command:
$ sudo chmod 0644 /etc/group
Rationale
The /etc/group file contains information regarding groups that are configured on the system. Protection of this file is important for system security.
OVAL test results details

Testing mode of /etc/group  oval:ssg-test_file_permissions_etc_group_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_etc_group_0:obj:1 of type file_object
FilepathFilterFilter
/etc/groupoval:ssg-exclude_symlinks__etc_group:ste:1oval:ssg-state_file_permissions_etc_group_0_mode_0644or_stricter_:ste:1
Verify Permissions on gshadow Filexccdf_org.ssgproject.content_rule_file_permissions_etc_gshadow mediumCCE-86975-0

Verify Permissions on gshadow File

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_etc_gshadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_etc_gshadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86975-0

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.7
Description
To properly set the permissions of /etc/gshadow, run the command:
$ sudo chmod 0000 /etc/gshadow
Rationale
The /etc/gshadow file contains group password hashes. Protection of this file is critical for system security.
OVAL test results details

Testing mode of /etc/gshadow  oval:ssg-test_file_permissions_etc_gshadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_etc_gshadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/gshadowoval:ssg-exclude_symlinks__etc_gshadow:ste:1oval:ssg-state_file_permissions_etc_gshadow_0_mode_0000or_stricter_:ste:1
Verify Permissions on passwd Filexccdf_org.ssgproject.content_rule_file_permissions_etc_passwd mediumCCE-90644-6

Verify Permissions on passwd File

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_etc_passwd
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_etc_passwd:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-90644-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.1
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/passwd, run the command:
$ sudo chmod 0644 /etc/passwd
Rationale
If the /etc/passwd file is writable by a group-owner or the world the risk of its compromise is increased. The file contains the list of accounts on the system and associated information, and protection of this file is critical for system security.
OVAL test results details

Testing mode of /etc/passwd  oval:ssg-test_file_permissions_etc_passwd_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_etc_passwd_0:obj:1 of type file_object
FilepathFilterFilter
/etc/passwdoval:ssg-exclude_symlinks__etc_passwd:ste:1oval:ssg-state_file_permissions_etc_passwd_0_mode_0644or_stricter_:ste:1
Verify Permissions on shadow Filexccdf_org.ssgproject.content_rule_file_permissions_etc_shadow mediumCCE-88433-8

Verify Permissions on shadow File

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_etc_shadow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_etc_shadow:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88433-8

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis7.1.5
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/shadow, run the command:
$ sudo chmod 0000 /etc/shadow
Rationale
The /etc/shadow file contains the list of local system accounts and stores password hashes. Protection of this file is critical for system security. Failure to give ownership of this file to root provides the designated owner with access to sensitive information which could weaken the system security posture.
OVAL test results details

Testing mode of /etc/shadow  oval:ssg-test_file_permissions_etc_shadow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_etc_shadow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/shadowoval:ssg-exclude_symlinks__etc_shadow:ste:1oval:ssg-state_file_permissions_etc_shadow_0_mode_0000or_stricter_:ste:1
Verify Permissions on /etc/shells Filexccdf_org.ssgproject.content_rule_file_permissions_etc_shells mediumCCE-89912-0

Verify Permissions on /etc/shells File

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_etc_shells
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_etc_shells:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89912-0

References:
nistAC-3, MP-2
anssiR50
cis7.1.9
Description
To properly set the permissions of /etc/shells, run the command:
$ sudo chmod 0644 /etc/shells
Rationale
The /etc/shells file contains the list of full pathnames to shells on the system. Since this file is used by many system programs this file should be protected.
OVAL test results details

Testing mode of /etc/shells  oval:ssg-test_file_permissions_etc_shells_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_etc_shells_0:obj:1 of type file_object
FilepathFilterFilter
/etc/shellsoval:ssg-exclude_symlinks__etc_shells:ste:1oval:ssg-state_file_permissions_etc_shells_0_mode_0644or_stricter_:ste:1
Verify that All World-Writable Directories Have Sticky Bits Setxccdf_org.ssgproject.content_rule_dir_perms_world_writable_sticky_bits mediumCCE-88397-5

Verify that All World-Writable Directories Have Sticky Bits Set

Rule IDxccdf_org.ssgproject.content_rule_dir_perms_world_writable_sticky_bits
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-dir_perms_world_writable_sticky_bits:def:1
Time2025-07-22T08:31:47+00:00
Severitymedium
Identifiers:

CCE-88397-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000138-GPOS-00069
anssiR54
cis7.1.11
pcidss42.2.6, 2.2
Description
When the so-called 'sticky bit' is set on a directory, only the owner of a given file may remove that file from the directory. Without the sticky bit, any user with write access to a directory may remove any file in the directory. Setting the sticky bit prevents users from removing each other's files. In cases where there is no reason for a directory to be world-writable, a better solution is to remove that permission rather than to set the sticky bit. However, if a directory is used by a particular application, consult that application's documentation instead of blindly changing modes.
To set the sticky bit on a world-writable directory DIR, run the following command:
$ sudo chmod +t DIR
        
Rationale
Failing to set the sticky bit on public directories allows unauthorized users to delete files in the directory structure.

The only authorized public directories are those temporary directories supplied with the system, or those designed to be temporary file repositories. The setting is normally reserved for directories used by the system, by users for temporary file storage (such as /tmp), and for directories requiring global read/write access.
Warnings
warning  This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of directories present on the system. It is not a problem in most cases, but especially systems with a large number of directories can be affected. See https://access.redhat.com/articles/6999111.
warning  Please note that there might be cases where the rule remediation cannot fix directory permissions. This can happen for example when running on a system with some immutable parts. These immutable parts cannot be remediated because they are read-only. Example of such directories can be OStree deployments located at /sysroot/ostree/deploy. In such case, it is needed to make modifications to the underlying ostree snapshot and this is out of scope of regular rule remediation.
OVAL test results details

Check the existence of world-writable directories without sticky bits  oval:ssg-test_dir_perms_world_writable_sticky_bits:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_dir_perms_world_writable_sticky_bits:obj:1 of type file_object
BehaviorsPathFilenameFilter
/boot/efi
/
no valueno valueoval:ssg-state_dir_perms_world_writable_sticky_bits:ste:1
Ensure No World-Writable Files Existxccdf_org.ssgproject.content_rule_file_permissions_unauthorized_world_writable mediumCCE-87656-5

Ensure No World-Writable Files Exist

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_unauthorized_world_writable
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_unauthorized_world_writable:def:1
Time2025-07-22T08:31:49+00:00
Severitymedium
Identifiers:

CCE-87656-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR54
cis7.1.11
pcidss42.2.6, 2.2
Description
It is generally a good idea to remove global (other) write access to a file when it is discovered. However, check with documentation for specific applications before making changes. Also, monitor for recurring world-writable files, as these may be symptoms of a misconfigured application or user account. Finally, this applies to real files and not virtual files that are a part of pseudo file systems such as sysfs or procfs.
Rationale
Data in world-writable files can be modified by any user on the system. In almost all circumstances, files can be configured using a combination of user and group permissions to support whatever legitimate access is needed without the risk caused by world-writable files.
Warnings
warning  This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See https://access.redhat.com/articles/6999111.
OVAL test results details

Check the existence of world-writable files  oval:ssg-test_file_permissions_unauthorized_world_write:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_unauthorized_world_write:obj:1 of type file_object
BehaviorsPathFilenameFilterFilterFilter
/boot/efi
/
no value^.*$oval:ssg-state_file_permissions_unauthorized_world_write:ste:1oval:ssg-state_file_permissions_unauthorized_world_write_special_selinux_files:ste:1oval:ssg-state_file_permissions_unauthorized_world_write_sysroot:ste:1
Ensure All Files Are Owned by a Groupxccdf_org.ssgproject.content_rule_file_permissions_ungroupowned mediumCCE-88305-8

Ensure All Files Are Owned by a Group

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_ungroupowned
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_ungroupowned:def:1
Time2025-07-22T08:31:54+00:00
Severitymedium
Identifiers:

CCE-88305-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.06, DSS06.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.18.1.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
anssiR53
cis7.1.12
pcidss42.2.6, 2.2
Description
If any file is not group-owned by a valid defined group, the cause of the lack of group-ownership must be investigated. Following this, those files should be deleted or assigned to an appropriate group. The groups need to be defined in /etc/group or in /usr/lib/group if nss-altfiles are configured to be used in /etc/nsswitch.conf. Locate the mount points related to local devices by the following command:
$ findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)
For all mount points listed by the previous command, it is necessary to search for files which do not belong to a valid group using the following command:
$ sudo find MOUNTPOINT -xdev -nogroup 2>/dev/null
Rationale
Unowned files do not directly imply a security problem, but they are generally a sign that something is amiss. They may be caused by an intruder, by incorrect software installation or draft software removal, or by failure to remove all files belonging to a deleted account, or other similar cases. The files should be repaired so they will not cause problems when accounts are created in the future, and the cause should be discovered and addressed.
Warnings
warning  This rule only considers local groups as valid groups. If you have your groups defined outside /etc/group or /usr/lib/group, the rule won't consider those.
warning  This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See https://access.redhat.com/articles/6999111.
OVAL test results details

Test if /etc/nssswitch.conf contains 'altfiles' in 'group' key  oval:ssg-test_file_permissions_ungroupowned_nsswitch_uses_altfiles:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/nsswitch.confgroup: files [SUCCESS=merge] systemd

there are no files with group owner different than local groups  oval:ssg-test_file_permissions_ungroupowned:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_ungroupowned:obj:1 of type file_object
BehaviorsPathFilenameFilterFilter
/boot/efi
/
3
4
0
5
997
81
59
996
104
36
105
106
2
19
1
6
190
114
995
994
74
993
992
1000
33
39
50
54
63
100
65534
22
35
999
998
18
15
12
11
10
9
8
7
20
no value.*oval:ssg-state_file_permissions_ungroupowned_local_group_owner:ste:1oval:ssg-state_file_permissions_ungroupowned_sysroot:ste:1

Test if /etc/nssswitch.conf contains 'altfiles' in 'group' key  oval:ssg-test_file_permissions_ungroupowned_nsswitch_uses_altfiles:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/nsswitch.confgroup: files [SUCCESS=merge] systemd

there are no files with group owner different than local groups  oval:ssg-test_file_permissions_ungroupowned_with_usrlib:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_ungroupowned_with_usrlib:obj:1 of type file_object
BehaviorsPathFilenameFilterFilter
/boot/efi
/
3
4
0
5
997
81
59
996
104
36
105
106
2
19
1
6
190
114
995
994
74
993
992
1000
33
39
50
54
63
100
65534
22
35
999
998
18
15
12
11
10
9
8
7
20
no value.*oval:ssg-state_file_permissions_ungroupowned_local_group_owner_with_usrlib:ste:1oval:ssg-state_file_permissions_ungroupowned_sysroot:ste:1
Ensure All Files Are Owned by a Userxccdf_org.ssgproject.content_rule_no_files_unowned_by_user mediumCCE-89680-3

Ensure All Files Are Owned by a User

Rule IDxccdf_org.ssgproject.content_rule_no_files_unowned_by_user
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-no_files_unowned_by_user:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89680-3

References:
cis-csc11, 12, 13, 14, 15, 16, 18, 3, 5, 9
cobit5APO01.06, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 5.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.AC-6, PR.DS-5, PR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
anssiR53
cis7.1.12
pcidss42.2.6, 2.2
Description
If any files are not owned by a user, then the cause of their lack of ownership should be investigated. Following this, the files should be deleted or assigned to an appropriate user. Locate the mount points related to local devices by the following command:
$ findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)
For all mount points listed by the previous command, it is necessary to search for files which do not belong to a valid user using the following command:
$ sudo find MOUNTPOINT -xdev -nouser 2>/dev/null
Rationale
Unowned files do not directly imply a security problem, but they are generally a sign that something is amiss. They may be caused by an intruder, by incorrect software installation or draft software removal, or by failure to remove all files belonging to a deleted account, or other similar cases. The files should be repaired so they will not cause problems when accounts are created in the future, and the cause should be discovered and addressed.
Warnings
warning  For this rule to evaluate centralized user accounts, getent must be working properly so that running the command
getent passwd
returns a list of all users in your organization. If using the System Security Services Daemon (SSSD),
enumerate = true
must be configured in your organization's domain to return a complete list of users
warning  This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See https://access.redhat.com/articles/6999111.
OVAL test results details

there are no files without a known owner  oval:ssg-test_no_files_unowned_by_user:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_no_files_unowned_by_user:obj:1 of type file_object
BehaviorsPathFilenameFilter
7
11
1
12
14
0
2
65534
999
998
81
59
997
114
996
995
74
994
992
1000
6
5
4
3
8
/boot/efi
/
no value.*oval:ssg-state_no_files_unowned_by_user_uids_list:ste:1
Disable the Automounterxccdf_org.ssgproject.content_rule_service_autofs_disabled mediumCCE-88947-7

Disable the Automounter

Rule IDxccdf_org.ssgproject.content_rule_service_autofs_disabled
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88947-7

References:
cis-csc1, 12, 15, 16, 5
cobit5APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.4.6
hipaa164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.310(d)(1), 164.310(d)(2), 164.312(a)(1), 164.312(a)(2)(iv), 164.312(b)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.6
iso27001-2013A.11.2.6, A.13.1.1, A.13.2.1, A.18.1.4, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-7(a), CM-7(b), CM-6(a), MP-7
nist-csfPR.AC-1, PR.AC-3, PR.AC-6, PR.AC-7
os-srgSRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227
cis2.1.1
Description
The autofs daemon mounts and unmounts filesystems, such as user home directories shared via NFS, on demand. In addition, autofs can be used to handle removable media, and the default configuration provides the cdrom device as /misc/cd. However, this method of providing access to removable media is not common, so autofs can almost always be disabled if NFS is not in use. Even if NFS is required, it may be possible to configure filesystem mounts statically by editing /etc/fstab rather than relying on the automounter.

The autofs service can be disabled with the following command:
$ sudo systemctl mask --now autofs.service
Rationale
Disabling the automounter permits the administrator to statically control filesystem mounting through /etc/fstab.

Additionally, automatically mounting filesystems permits easy introduction of unknown devices, thereby facilitating malicious activity.
Disable Mounting of squashfsxccdf_org.ssgproject.content_rule_kernel_module_squashfs_disabled lowCCE-88216-7

Disable Mounting of squashfs

Rule IDxccdf_org.ssgproject.content_rule_kernel_module_squashfs_disabled
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-kernel_module_squashfs_disabled:def:1
Time2025-07-22T08:31:56+00:00
Severitylow
Identifiers:

CCE-88216-7

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui3.4.6
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
cis1.1.1.6
Description
To configure the system to prevent the squashfs kernel module from being loaded, add the following line to the file /etc/modprobe.d/squashfs.conf:
install squashfs /bin/false
This effectively prevents usage of this uncommon filesystem. The squashfs filesystem type is a compressed read-only Linux filesystem embedded in small footprint systems (similar to cramfs). A squashfs image can be used without having to first decompress the image.
Rationale
Removing support for unneeded filesystem types reduces the local attack surface of the system.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if LC_ALL=C grep -q -m 1 "^install squashfs" /etc/modprobe.d/squashfs.conf ; then
	
	sed -i 's#^install squashfs.*#install squashfs /bin/false#g' /etc/modprobe.d/squashfs.conf
else
	echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/squashfs.conf
	echo "install squashfs /bin/false" >> /etc/modprobe.d/squashfs.conf
fi

if ! LC_ALL=C grep -q -m 1 "^blacklist squashfs$" /etc/modprobe.d/squashfs.conf ; then
	echo "blacklist squashfs" >> /etc/modprobe.d/squashfs.conf
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88216-7
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - kernel_module_squashfs_disabled
  - low_complexity
  - low_severity
  - medium_disruption
  - reboot_required

- name: Ensure kernel module 'squashfs' is disabled
  lineinfile:
    create: true
    dest: /etc/modprobe.d/squashfs.conf
    regexp: install\s+squashfs
    line: install squashfs /bin/false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88216-7
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - kernel_module_squashfs_disabled
  - low_complexity
  - low_severity
  - medium_disruption
  - reboot_required

- name: Ensure kernel module 'squashfs' is blacklisted
  lineinfile:
    create: true
    dest: /etc/modprobe.d/squashfs.conf
    regexp: ^blacklist squashfs$
    line: blacklist squashfs
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88216-7
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - kernel_module_squashfs_disabled
  - low_complexity
  - low_severity
  - medium_disruption
  - reboot_required

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,install%20squashfs%20/bin/false%0Ablacklist%20squashfs%0A
        mode: 0644
        path: /etc/modprobe.d/squashfs.conf
        overwrite: true
OVAL test results details

kernel module squashfs blacklisted  oval:ssg-test_kernmod_squashfs_blacklisted:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_squashfs_blacklisted:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^blacklist\s+squashfs$1

kernel module squashfs disabled  oval:ssg-test_kernmod_squashfs_disabled:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_squashfs_disabled:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^\s*install\s+squashfs\s+(/bin/false|/bin/true)$1
Disable Mounting of udfxccdf_org.ssgproject.content_rule_kernel_module_udf_disabled lowCCE-87504-7

Disable Mounting of udf

Rule IDxccdf_org.ssgproject.content_rule_kernel_module_udf_disabled
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-kernel_module_udf_disabled:def:1
Time2025-07-22T08:31:56+00:00
Severitylow
Identifiers:

CCE-87504-7

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui3.4.6
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
cis1.1.1.7
Description
To configure the system to prevent the udf kernel module from being loaded, add the following line to the file /etc/modprobe.d/udf.conf:
install udf /bin/false
This effectively prevents usage of this uncommon filesystem. The udf filesystem type is the universal disk format used to implement the ISO/IEC 13346 and ECMA-167 specifications. This is an open vendor filesystem type for data storage on a broad range of media. This filesystem type is neccessary to support writing DVDs and newer optical disc formats.
Rationale
Removing support for unneeded filesystem types reduces the local attack surface of the system.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if LC_ALL=C grep -q -m 1 "^install udf" /etc/modprobe.d/udf.conf ; then
	
	sed -i 's#^install udf.*#install udf /bin/false#g' /etc/modprobe.d/udf.conf
else
	echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/udf.conf
	echo "install udf /bin/false" >> /etc/modprobe.d/udf.conf
fi

if ! LC_ALL=C grep -q -m 1 "^blacklist udf$" /etc/modprobe.d/udf.conf ; then
	echo "blacklist udf" >> /etc/modprobe.d/udf.conf
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87504-7
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - kernel_module_udf_disabled
  - low_complexity
  - low_severity
  - medium_disruption
  - reboot_required

- name: Ensure kernel module 'udf' is disabled
  lineinfile:
    create: true
    dest: /etc/modprobe.d/udf.conf
    regexp: install\s+udf
    line: install udf /bin/false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87504-7
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - kernel_module_udf_disabled
  - low_complexity
  - low_severity
  - medium_disruption
  - reboot_required

- name: Ensure kernel module 'udf' is blacklisted
  lineinfile:
    create: true
    dest: /etc/modprobe.d/udf.conf
    regexp: ^blacklist udf$
    line: blacklist udf
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87504-7
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - disable_strategy
  - kernel_module_udf_disabled
  - low_complexity
  - low_severity
  - medium_disruption
  - reboot_required

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,install%20udf%20/bin/false%0Ablacklist%20udf%0A
        mode: 0644
        path: /etc/modprobe.d/udf.conf
        overwrite: true
OVAL test results details

kernel module udf blacklisted  oval:ssg-test_kernmod_udf_blacklisted:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_udf_blacklisted:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^blacklist\s+udf$1

kernel module udf disabled  oval:ssg-test_kernmod_udf_disabled:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_udf_disabled:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^\s*install\s+udf\s+(/bin/false|/bin/true)$1
Disable Modprobe Loading of USB Storage Driverxccdf_org.ssgproject.content_rule_kernel_module_usb-storage_disabled mediumCCE-89301-6

Disable Modprobe Loading of USB Storage Driver

Rule IDxccdf_org.ssgproject.content_rule_kernel_module_usb-storage_disabled
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-kernel_module_usb-storage_disabled:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89301-6

References:
cis-csc1, 12, 15, 16, 5
cobit5APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.1.21
hipaa164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.310(d)(1), 164.310(d)(2), 164.312(a)(1), 164.312(a)(2)(iv), 164.312(b)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.6
iso27001-2013A.11.2.6, A.13.1.1, A.13.2.1, A.18.1.4, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-7(a), CM-7(b), CM-6(a), MP-7
nist-csfPR.AC-1, PR.AC-3, PR.AC-6, PR.AC-7
os-srgSRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227
app-srg-ctrSRG-APP-000141-CTR-000315
cis1.1.1.8
pcidss43.4.2, 3.4
Description
To prevent USB storage devices from being used, configure the kernel module loading system to prevent automatic loading of the USB storage driver. To configure the system to prevent the usb-storage kernel module from being loaded, add the following line to the file /etc/modprobe.d/usb-storage.conf:
install usb-storage /bin/false
This will prevent the modprobe program from loading the usb-storage module, but will not prevent an administrator (or another program) from using the insmod program to load the module manually.
Rationale
USB storage devices such as thumb drives can be used to introduce malicious software.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if LC_ALL=C grep -q -m 1 "^install usb-storage" /etc/modprobe.d/usb-storage.conf ; then
	
	sed -i 's#^install usb-storage.*#install usb-storage /bin/false#g' /etc/modprobe.d/usb-storage.conf
else
	echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/usb-storage.conf
	echo "install usb-storage /bin/false" >> /etc/modprobe.d/usb-storage.conf
fi

if ! LC_ALL=C grep -q -m 1 "^blacklist usb-storage$" /etc/modprobe.d/usb-storage.conf ; then
	echo "blacklist usb-storage" >> /etc/modprobe.d/usb-storage.conf
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89301-6
  - NIST-800-171-3.1.21
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - PCI-DSSv4-3.4
  - PCI-DSSv4-3.4.2
  - disable_strategy
  - kernel_module_usb-storage_disabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required

- name: Ensure kernel module 'usb-storage' is disabled
  lineinfile:
    create: true
    dest: /etc/modprobe.d/usb-storage.conf
    regexp: install\s+usb-storage
    line: install usb-storage /bin/false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89301-6
  - NIST-800-171-3.1.21
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - PCI-DSSv4-3.4
  - PCI-DSSv4-3.4.2
  - disable_strategy
  - kernel_module_usb-storage_disabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required

- name: Ensure kernel module 'usb-storage' is blacklisted
  lineinfile:
    create: true
    dest: /etc/modprobe.d/usb-storage.conf
    regexp: ^blacklist usb-storage$
    line: blacklist usb-storage
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89301-6
  - NIST-800-171-3.1.21
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - PCI-DSSv4-3.4
  - PCI-DSSv4-3.4.2
  - disable_strategy
  - kernel_module_usb-storage_disabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,install%20usb-storage%20/bin/false%0Ablacklist%20usb-storage%0A
        mode: 0644
        path: /etc/modprobe.d/usb-storage.conf
        overwrite: true
OVAL test results details

kernel module usb-storage blacklisted  oval:ssg-test_kernmod_usb-storage_blacklisted:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_usb-storage_blacklisted:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^blacklist\s+usb-storage$1

kernel module usb-storage disabled  oval:ssg-test_kernmod_usb-storage_disabled:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_kernmod_usb-storage_disabled:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/modprobe.d
/etc/modules-load.d
/run/modprobe.d
/run/modules-load.d
/usr/lib/modprobe.d
/usr/lib/modules-load.d
^.*\.conf$^\s*install\s+usb-storage\s+(/bin/false|/bin/true)$1
Add nodev Option to /dev/shmxccdf_org.ssgproject.content_rule_mount_option_dev_shm_nodev mediumCCE-86783-8

Add nodev Option to /dev/shm

Rule IDxccdf_org.ssgproject.content_rule_mount_option_dev_shm_nodev
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-mount_option_dev_shm_nodev:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86783-8

References:
cis-csc11, 13, 14, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.11.2.9, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.2.2
Description
The nodev mount option can be used to prevent creation of device files in /dev/shm. Legitimate character and block devices should not exist within temporary directories like /dev/shm. Add the nodev option to the fourth column of /etc/fstab for the line which controls mounting of /dev/shm.
Rationale
The only legitimate location for device files is the /dev directory located on the root partition. The only exception to this is chroot jails.

Reboot:false
# Remediation is applicable only in certain platforms
if ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} ) && ! ( [ -f /.dockerenv ] || [ -f /run/.containerenv ] ) ); then

function perform_remediation {
    


    mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /dev/shm)"

    # If the mount point is not in /etc/fstab, get previous mount options from /etc/mtab
    if ! grep -q "$mount_point_match_regexp" /etc/fstab; then
        # runtime opts without some automatic kernel/userspace-added defaults
        previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/mtab | head -1 |  awk '{print $4}' \
                    | sed -E "s/(rw|defaults|seclabel|nodev)(,|$)//g;s/,$//")
        [ "$previous_mount_opts" ] && previous_mount_opts+=","
        # In iso9660 filesystems mtab could describe a "blocksize" value, this should be reflected in
        # fstab as "block".  The next variable is to satisfy shellcheck SC2050.
        fs_type="tmpfs"
        if [  "$fs_type" == "iso9660" ] ; then
            previous_mount_opts=$(sed 's/blocksize=/block=/' <<< "$previous_mount_opts")
        fi
        echo "tmpfs /dev/shm tmpfs defaults,${previous_mount_opts}nodev 0 0" >> /etc/fstab
    # If the mount_opt option is not already in the mount point's /etc/fstab entry, add it
    elif ! grep "$mount_point_match_regexp" /etc/fstab | grep -q "nodev"; then
        previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/fstab | awk '{print $4}')
        sed -i "s|\(${mount_point_match_regexp}.*${previous_mount_opts}\)|\1,nodev|" /etc/fstab
    fi


    if mkdir -p "/dev/shm"; then
        if mountpoint -q "/dev/shm"; then
            mount -o remount --target "/dev/shm"
        fi
    fi
}

perform_remediation

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:high
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86783-8
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nodev
  - no_reboot_needed

- name: 'Add nodev Option to /dev/shm: Check information associated to mountpoint'
  command: findmnt  '/dev/shm'
  register: device_name
  failed_when: device_name.rc > 1
  changed_when: false
  when: ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  tags:
  - CCE-86783-8
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nodev
  - no_reboot_needed

- name: 'Add nodev Option to /dev/shm: Create mount_info dictionary variable'
  set_fact:
    mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
  with_together:
  - '{{ device_name.stdout_lines[0].split() | list | lower }}'
  - '{{ device_name.stdout_lines[1].split() | list }}'
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - device_name.stdout is defined and device_name.stdout_lines is defined
  - (device_name.stdout | length > 0)
  tags:
  - CCE-86783-8
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nodev
  - no_reboot_needed

- name: 'Add nodev Option to /dev/shm: If /dev/shm not mounted, craft mount_info manually'
  set_fact:
    mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
  with_together:
  - - target
    - source
    - fstype
    - options
  - - /dev/shm
    - tmpfs
    - tmpfs
    - defaults
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - ("" | length == 0)
  - device_name.stdout is defined and device_name.stdout_lines is defined
  - (device_name.stdout | length == 0)
  tags:
  - CCE-86783-8
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nodev
  - no_reboot_needed

- name: 'Add nodev Option to /dev/shm: Make sure nodev option is part of the to /dev/shm
    options'
  set_fact:
    mount_info: '{{ mount_info | combine( {''options'':''''~mount_info.options~'',nodev''
      }) }}'
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - mount_info is defined and "nodev" not in mount_info.options
  tags:
  - CCE-86783-8
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nodev
  - no_reboot_needed

- name: 'Add nodev Option to /dev/shm: Ensure /dev/shm is mounted with nodev option'
  mount:
    path: /dev/shm
    src: '{{ mount_info.source }}'
    opts: '{{ mount_info.options }}'
    state: mounted
    fstype: '{{ mount_info.fstype }}'
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - mount_info is defined
  - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("" |
    length == 0)
  tags:
  - CCE-86783-8
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nodev
  - no_reboot_needed
OVAL test results details

nodev on /dev/shm   oval:ssg-test_dev_shm_partition_nodev_expected:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMount pointDeviceUuidFs typeMount optionsMount optionsMount optionsMount optionsMount optionsTotal spaceSpace usedSpace left
true/dev/shmtmpfstmpfsrwseclabelnosuidnodevinode642108950210895

/dev/shm exists  oval:ssg-test_dev_shm_partition_nodev_expected_exist:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMount pointDeviceUuidFs typeMount optionsMount optionsMount optionsMount optionsMount optionsTotal spaceSpace usedSpace left
not evaluated/dev/shmtmpfstmpfsrwseclabelnosuidnodevinode642108950210895

nodev on /dev/shm in /etc/fstab  oval:ssg-test_dev_shm_partition_nodev_expected_in_fstab:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_dev_shm_partition_nodev_expected_in_fstab:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/fstab^[\s]*(?!#)[\S]+[\s]+/dev/shm[\s]+[\S]+[\s]+([\S]+)1
Add noexec Option to /dev/shmxccdf_org.ssgproject.content_rule_mount_option_dev_shm_noexec mediumCCE-86775-4

Add noexec Option to /dev/shm

Rule IDxccdf_org.ssgproject.content_rule_mount_option_dev_shm_noexec
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-mount_option_dev_shm_noexec:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86775-4

References:
cis-csc11, 13, 14, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.11.2.9, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.2.4
Description
The noexec mount option can be used to prevent binaries from being executed out of /dev/shm. It can be dangerous to allow the execution of binaries from world-writable temporary storage directories such as /dev/shm. Add the noexec option to the fourth column of /etc/fstab for the line which controls mounting of /dev/shm.
Rationale
Allowing users to execute binaries from world-writable directories such as /dev/shm can expose the system to potential compromise.

Reboot:false
# Remediation is applicable only in certain platforms
if ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} ) && ! ( [ -f /.dockerenv ] || [ -f /run/.containerenv ] ) ); then

function perform_remediation {
    


    mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /dev/shm)"

    # If the mount point is not in /etc/fstab, get previous mount options from /etc/mtab
    if ! grep -q "$mount_point_match_regexp" /etc/fstab; then
        # runtime opts without some automatic kernel/userspace-added defaults
        previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/mtab | head -1 |  awk '{print $4}' \
                    | sed -E "s/(rw|defaults|seclabel|noexec)(,|$)//g;s/,$//")
        [ "$previous_mount_opts" ] && previous_mount_opts+=","
        # In iso9660 filesystems mtab could describe a "blocksize" value, this should be reflected in
        # fstab as "block".  The next variable is to satisfy shellcheck SC2050.
        fs_type="tmpfs"
        if [  "$fs_type" == "iso9660" ] ; then
            previous_mount_opts=$(sed 's/blocksize=/block=/' <<< "$previous_mount_opts")
        fi
        echo "tmpfs /dev/shm tmpfs defaults,${previous_mount_opts}noexec 0 0" >> /etc/fstab
    # If the mount_opt option is not already in the mount point's /etc/fstab entry, add it
    elif ! grep "$mount_point_match_regexp" /etc/fstab | grep -q "noexec"; then
        previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/fstab | awk '{print $4}')
        sed -i "s|\(${mount_point_match_regexp}.*${previous_mount_opts}\)|\1,noexec|" /etc/fstab
    fi


    if mkdir -p "/dev/shm"; then
        if mountpoint -q "/dev/shm"; then
            mount -o remount --target "/dev/shm"
        fi
    fi
}

perform_remediation

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:high
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86775-4
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_noexec
  - no_reboot_needed

- name: 'Add noexec Option to /dev/shm: Check information associated to mountpoint'
  command: findmnt  '/dev/shm'
  register: device_name
  failed_when: device_name.rc > 1
  changed_when: false
  when: ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  tags:
  - CCE-86775-4
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_noexec
  - no_reboot_needed

- name: 'Add noexec Option to /dev/shm: Create mount_info dictionary variable'
  set_fact:
    mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
  with_together:
  - '{{ device_name.stdout_lines[0].split() | list | lower }}'
  - '{{ device_name.stdout_lines[1].split() | list }}'
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - device_name.stdout is defined and device_name.stdout_lines is defined
  - (device_name.stdout | length > 0)
  tags:
  - CCE-86775-4
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_noexec
  - no_reboot_needed

- name: 'Add noexec Option to /dev/shm: If /dev/shm not mounted, craft mount_info
    manually'
  set_fact:
    mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
  with_together:
  - - target
    - source
    - fstype
    - options
  - - /dev/shm
    - tmpfs
    - tmpfs
    - defaults
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - ("" | length == 0)
  - device_name.stdout is defined and device_name.stdout_lines is defined
  - (device_name.stdout | length == 0)
  tags:
  - CCE-86775-4
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_noexec
  - no_reboot_needed

- name: 'Add noexec Option to /dev/shm: Make sure noexec option is part of the to
    /dev/shm options'
  set_fact:
    mount_info: '{{ mount_info | combine( {''options'':''''~mount_info.options~'',noexec''
      }) }}'
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - mount_info is defined and "noexec" not in mount_info.options
  tags:
  - CCE-86775-4
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_noexec
  - no_reboot_needed

- name: 'Add noexec Option to /dev/shm: Ensure /dev/shm is mounted with noexec option'
  mount:
    path: /dev/shm
    src: '{{ mount_info.source }}'
    opts: '{{ mount_info.options }}'
    state: mounted
    fstype: '{{ mount_info.fstype }}'
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - mount_info is defined
  - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("" |
    length == 0)
  tags:
  - CCE-86775-4
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_noexec
  - no_reboot_needed
OVAL test results details

noexec on /dev/shm   oval:ssg-test_dev_shm_partition_noexec_expected:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMount pointDeviceUuidFs typeMount optionsMount optionsMount optionsMount optionsMount optionsTotal spaceSpace usedSpace left
false/dev/shmtmpfstmpfsrwseclabelnosuidnodevinode642108950210895

/dev/shm exists  oval:ssg-test_dev_shm_partition_noexec_expected_exist:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMount pointDeviceUuidFs typeMount optionsMount optionsMount optionsMount optionsMount optionsTotal spaceSpace usedSpace left
not evaluated/dev/shmtmpfstmpfsrwseclabelnosuidnodevinode642108950210895

noexec on /dev/shm in /etc/fstab  oval:ssg-test_dev_shm_partition_noexec_expected_in_fstab:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_dev_shm_partition_noexec_expected_in_fstab:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/fstab^[\s]*(?!#)[\S]+[\s]+/dev/shm[\s]+[\S]+[\s]+([\S]+)1
Add nosuid Option to /dev/shmxccdf_org.ssgproject.content_rule_mount_option_dev_shm_nosuid mediumCCE-88358-7

Add nosuid Option to /dev/shm

Rule IDxccdf_org.ssgproject.content_rule_mount_option_dev_shm_nosuid
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-mount_option_dev_shm_nosuid:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88358-7

References:
cis-csc11, 13, 14, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.11.2.9, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.2.3
Description
The nosuid mount option can be used to prevent execution of setuid programs in /dev/shm. The SUID and SGID permissions should not be required in these world-writable directories. Add the nosuid option to the fourth column of /etc/fstab for the line which controls mounting of /dev/shm.
Rationale
The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from temporary storage partitions.

Reboot:false
# Remediation is applicable only in certain platforms
if ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} ) && ! ( [ -f /.dockerenv ] || [ -f /run/.containerenv ] ) ); then

function perform_remediation {
    


    mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /dev/shm)"

    # If the mount point is not in /etc/fstab, get previous mount options from /etc/mtab
    if ! grep -q "$mount_point_match_regexp" /etc/fstab; then
        # runtime opts without some automatic kernel/userspace-added defaults
        previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/mtab | head -1 |  awk '{print $4}' \
                    | sed -E "s/(rw|defaults|seclabel|nosuid)(,|$)//g;s/,$//")
        [ "$previous_mount_opts" ] && previous_mount_opts+=","
        # In iso9660 filesystems mtab could describe a "blocksize" value, this should be reflected in
        # fstab as "block".  The next variable is to satisfy shellcheck SC2050.
        fs_type="tmpfs"
        if [  "$fs_type" == "iso9660" ] ; then
            previous_mount_opts=$(sed 's/blocksize=/block=/' <<< "$previous_mount_opts")
        fi
        echo "tmpfs /dev/shm tmpfs defaults,${previous_mount_opts}nosuid 0 0" >> /etc/fstab
    # If the mount_opt option is not already in the mount point's /etc/fstab entry, add it
    elif ! grep "$mount_point_match_regexp" /etc/fstab | grep -q "nosuid"; then
        previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/fstab | awk '{print $4}')
        sed -i "s|\(${mount_point_match_regexp}.*${previous_mount_opts}\)|\1,nosuid|" /etc/fstab
    fi


    if mkdir -p "/dev/shm"; then
        if mountpoint -q "/dev/shm"; then
            mount -o remount --target "/dev/shm"
        fi
    fi
}

perform_remediation

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:high
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88358-7
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nosuid
  - no_reboot_needed

- name: 'Add nosuid Option to /dev/shm: Check information associated to mountpoint'
  command: findmnt  '/dev/shm'
  register: device_name
  failed_when: device_name.rc > 1
  changed_when: false
  when: ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  tags:
  - CCE-88358-7
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nosuid
  - no_reboot_needed

- name: 'Add nosuid Option to /dev/shm: Create mount_info dictionary variable'
  set_fact:
    mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
  with_together:
  - '{{ device_name.stdout_lines[0].split() | list | lower }}'
  - '{{ device_name.stdout_lines[1].split() | list }}'
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - device_name.stdout is defined and device_name.stdout_lines is defined
  - (device_name.stdout | length > 0)
  tags:
  - CCE-88358-7
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nosuid
  - no_reboot_needed

- name: 'Add nosuid Option to /dev/shm: If /dev/shm not mounted, craft mount_info
    manually'
  set_fact:
    mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
  with_together:
  - - target
    - source
    - fstype
    - options
  - - /dev/shm
    - tmpfs
    - tmpfs
    - defaults
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - ("" | length == 0)
  - device_name.stdout is defined and device_name.stdout_lines is defined
  - (device_name.stdout | length == 0)
  tags:
  - CCE-88358-7
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nosuid
  - no_reboot_needed

- name: 'Add nosuid Option to /dev/shm: Make sure nosuid option is part of the to
    /dev/shm options'
  set_fact:
    mount_info: '{{ mount_info | combine( {''options'':''''~mount_info.options~'',nosuid''
      }) }}'
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - mount_info is defined and "nosuid" not in mount_info.options
  tags:
  - CCE-88358-7
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nosuid
  - no_reboot_needed

- name: 'Add nosuid Option to /dev/shm: Ensure /dev/shm is mounted with nosuid option'
  mount:
    path: /dev/shm
    src: '{{ mount_info.source }}'
    opts: '{{ mount_info.options }}'
    state: mounted
    fstype: '{{ mount_info.fstype }}'
  when:
  - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
    and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
    ) and not ( ansible_virtualization_type in ["docker", "lxc", "openvz", "podman",
    "container"] ) )
  - mount_info is defined
  - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("" |
    length == 0)
  tags:
  - CCE-88358-7
  - NIST-800-53-AC-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - configure_strategy
  - high_disruption
  - low_complexity
  - medium_severity
  - mount_option_dev_shm_nosuid
  - no_reboot_needed
OVAL test results details

nosuid on /dev/shm   oval:ssg-test_dev_shm_partition_nosuid_expected:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMount pointDeviceUuidFs typeMount optionsMount optionsMount optionsMount optionsMount optionsTotal spaceSpace usedSpace left
true/dev/shmtmpfstmpfsrwseclabelnosuidnodevinode642108950210895

/dev/shm exists  oval:ssg-test_dev_shm_partition_nosuid_expected_exist:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMount pointDeviceUuidFs typeMount optionsMount optionsMount optionsMount optionsMount optionsTotal spaceSpace usedSpace left
not evaluated/dev/shmtmpfstmpfsrwseclabelnosuidnodevinode642108950210895

nosuid on /dev/shm in /etc/fstab  oval:ssg-test_dev_shm_partition_nosuid_expected_in_fstab:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_dev_shm_partition_nosuid_expected_in_fstab:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/fstab^[\s]*(?!#)[\S]+[\s]+/dev/shm[\s]+[\S]+[\s]+([\S]+)1
Add nodev Option to /homexccdf_org.ssgproject.content_rule_mount_option_home_nodev unknownCCE-87344-8

Add nodev Option to /home

Rule IDxccdf_org.ssgproject.content_rule_mount_option_home_nodev
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severityunknown
Identifiers:

CCE-87344-8

References:
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.3.2
Description
The nodev mount option can be used to prevent device files from being created in /home. Legitimate character and block devices should exist only in the /dev directory on the root partition or within chroot jails built for system services. Add the nodev option to the fourth column of /etc/fstab for the line which controls mounting of /home.
Rationale
The only legitimate location for device files is the /dev directory located on the root partition. The only exception to this is chroot jails.
Add nosuid Option to /homexccdf_org.ssgproject.content_rule_mount_option_home_nosuid mediumCCE-88987-3

Add nosuid Option to /home

Rule IDxccdf_org.ssgproject.content_rule_mount_option_home_nosuid
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88987-3

References:
cis-csc11, 13, 14, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.11.2.9, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154, SRG-OS-000480-GPOS-00227
anssiR28
cis1.1.2.3.3
Description
The nosuid mount option can be used to prevent execution of setuid programs in /home. The SUID and SGID permissions should not be required in these user data directories. Add the nosuid option to the fourth column of /etc/fstab for the line which controls mounting of /home.
Rationale
The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from user home directory partitions.
Add nodev Option to /tmpxccdf_org.ssgproject.content_rule_mount_option_tmp_nodev mediumCCE-90522-4

Add nodev Option to /tmp

Rule IDxccdf_org.ssgproject.content_rule_mount_option_tmp_nodev
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-90522-4

References:
cis-csc11, 13, 14, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.11.2.9, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.1.2
Description
The nodev mount option can be used to prevent device files from being created in /tmp. Legitimate character and block devices should not exist within temporary directories like /tmp. Add the nodev option to the fourth column of /etc/fstab for the line which controls mounting of /tmp.
Rationale
The only legitimate location for device files is the /dev directory located on the root partition. The only exception to this is chroot jails.
Add noexec Option to /tmpxccdf_org.ssgproject.content_rule_mount_option_tmp_noexec mediumCCE-87095-6

Add noexec Option to /tmp

Rule IDxccdf_org.ssgproject.content_rule_mount_option_tmp_noexec
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87095-6

References:
cis-csc11, 13, 14, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.11.2.9, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
anssiR28
cis1.1.2.1.4
Description
The noexec mount option can be used to prevent binaries from being executed out of /tmp. Add the noexec option to the fourth column of /etc/fstab for the line which controls mounting of /tmp.
Rationale
Allowing users to execute binaries from world-writable directories such as /tmp should never be necessary in normal operation and can expose the system to potential compromise.
Add nosuid Option to /tmpxccdf_org.ssgproject.content_rule_mount_option_tmp_nosuid mediumCCE-87318-2

Add nosuid Option to /tmp

Rule IDxccdf_org.ssgproject.content_rule_mount_option_tmp_nosuid
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87318-2

References:
cis-csc11, 13, 14, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.11.2.9, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
anssiR28
cis1.1.2.1.3
Description
The nosuid mount option can be used to prevent execution of setuid programs in /tmp. The SUID and SGID permissions should not be required in these world-writable directories. Add the nosuid option to the fourth column of /etc/fstab for the line which controls mounting of /tmp.
Rationale
The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from temporary storage partitions.
Add nodev Option to /var/log/auditxccdf_org.ssgproject.content_rule_mount_option_var_log_audit_nodev mediumCCE-87220-0

Add nodev Option to /var/log/audit

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_log_audit_nodev
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87220-0

References:
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
osppFMT_SMF_EXT.1
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.7.2
Description
The nodev mount option can be used to prevent device files from being created in /var/log/audit. Legitimate character and block devices should exist only in the /dev directory on the root partition or within chroot jails built for system services. Add the nodev option to the fourth column of /etc/fstab for the line which controls mounting of /var/log/audit.
Rationale
The only legitimate location for device files is the /dev directory located on the root partition. The only exception to this is chroot jails.
Add noexec Option to /var/log/auditxccdf_org.ssgproject.content_rule_mount_option_var_log_audit_noexec mediumCCE-88957-6

Add noexec Option to /var/log/audit

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_log_audit_noexec
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88957-6

References:
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
osppFMT_SMF_EXT.1
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.7.4
Description
The noexec mount option can be used to prevent binaries from being executed out of /var/log/audit. Add the noexec option to the fourth column of /etc/fstab for the line which controls mounting of /var/log/audit.
Rationale
Allowing users to execute binaries from directories containing audit log files such as /var/log/audit should never be necessary in normal operation and can expose the system to potential compromise.
Add nosuid Option to /var/log/auditxccdf_org.ssgproject.content_rule_mount_option_var_log_audit_nosuid mediumCCE-90694-1

Add nosuid Option to /var/log/audit

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_log_audit_nosuid
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-90694-1

References:
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
osppFMT_SMF_EXT.1
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.7.3
Description
The nosuid mount option can be used to prevent execution of setuid programs in /var/log/audit. The SUID and SGID permissions should not be required in directories containing audit log files. Add the nosuid option to the fourth column of /etc/fstab for the line which controls mounting of /var/log/audit.
Rationale
The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from partitions designated for audit log files.
Add nodev Option to /var/logxccdf_org.ssgproject.content_rule_mount_option_var_log_nodev mediumCCE-89389-1

Add nodev Option to /var/log

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_log_nodev
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89389-1

References:
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.6.2
Description
The nodev mount option can be used to prevent device files from being created in /var/log. Legitimate character and block devices should exist only in the /dev directory on the root partition or within chroot jails built for system services. Add the nodev option to the fourth column of /etc/fstab for the line which controls mounting of /var/log.
Rationale
The only legitimate location for device files is the /dev directory located on the root partition. The only exception to this is chroot jails.
Add noexec Option to /var/logxccdf_org.ssgproject.content_rule_mount_option_var_log_noexec mediumCCE-89129-1

Add noexec Option to /var/log

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_log_noexec
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89129-1

References:
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
anssiR28
cis1.1.2.6.4
Description
The noexec mount option can be used to prevent binaries from being executed out of /var/log. Add the noexec option to the fourth column of /etc/fstab for the line which controls mounting of /var/log.
Rationale
Allowing users to execute binaries from directories containing log files such as /var/log should never be necessary in normal operation and can expose the system to potential compromise.
Add nosuid Option to /var/logxccdf_org.ssgproject.content_rule_mount_option_var_log_nosuid mediumCCE-90639-6

Add nosuid Option to /var/log

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_log_nosuid
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-90639-6

References:
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
anssiR28
cis1.1.2.6.3
Description
The nosuid mount option can be used to prevent execution of setuid programs in /var/log. The SUID and SGID permissions should not be required in directories containing log files. Add the nosuid option to the fourth column of /etc/fstab for the line which controls mounting of /var/log.
Rationale
The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from partitions designated for log files.
Add nodev Option to /varxccdf_org.ssgproject.content_rule_mount_option_var_nodev mediumCCE-87070-9

Add nodev Option to /var

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_nodev
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87070-9

References:
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.4.2
Description
The nodev mount option can be used to prevent device files from being created in /var. Legitimate character and block devices should exist only in the /dev directory on the root partition or within chroot jails built for system services. Add the nodev option to the fourth column of /etc/fstab for the line which controls mounting of /var.
Rationale
The only legitimate location for device files is the /dev directory located on the root partition. The only exception to this is chroot jails.
Add nosuid Option to /varxccdf_org.ssgproject.content_rule_mount_option_var_nosuid mediumCCE-89496-4

Add nosuid Option to /var

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_nosuid
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89496-4

References:
anssiR28
cis1.1.2.4.3
Description
The nosuid mount option can be used to prevent execution of setuid programs in /var. The SUID and SGID permissions should not be required for this directory. Add the nosuid option to the fourth column of /etc/fstab for the line which controls mounting of /var.
Rationale
The presence of SUID and SGID executables should be tightly controlled.
Add nodev Option to /var/tmpxccdf_org.ssgproject.content_rule_mount_option_var_tmp_nodev mediumCCE-89441-0

Add nodev Option to /var/tmp

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_tmp_nodev
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-89441-0

References:
os-srgSRG-OS-000368-GPOS-00154
cis1.1.2.5.2
Description
The nodev mount option can be used to prevent device files from being created in /var/tmp. Legitimate character and block devices should not exist within temporary directories like /var/tmp. Add the nodev option to the fourth column of /etc/fstab for the line which controls mounting of /var/tmp.
Rationale
The only legitimate location for device files is the /dev directory located on the root partition. The only exception to this is chroot jails.
Add noexec Option to /var/tmpxccdf_org.ssgproject.content_rule_mount_option_var_tmp_noexec mediumCCE-87347-1

Add noexec Option to /var/tmp

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_tmp_noexec
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87347-1

References:
os-srgSRG-OS-000368-GPOS-00154
anssiR28
cis1.1.2.5.4
Description
The noexec mount option can be used to prevent binaries from being executed out of /var/tmp. Add the noexec option to the fourth column of /etc/fstab for the line which controls mounting of /var/tmp.
Rationale
Allowing users to execute binaries from world-writable directories such as /var/tmp should never be necessary in normal operation and can expose the system to potential compromise.
Add nosuid Option to /var/tmpxccdf_org.ssgproject.content_rule_mount_option_var_tmp_nosuid mediumCCE-87892-6

Add nosuid Option to /var/tmp

Rule IDxccdf_org.ssgproject.content_rule_mount_option_var_tmp_nosuid
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87892-6

References:
os-srgSRG-OS-000368-GPOS-00154
anssiR28
cis1.1.2.5.3
Description
The nosuid mount option can be used to prevent execution of setuid programs in /var/tmp. The SUID and SGID permissions should not be required in these world-writable directories. Add the nosuid option to the fourth column of /etc/fstab for the line which controls mounting of /var/tmp.
Rationale
The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from temporary storage partitions.
Disable core dump backtracesxccdf_org.ssgproject.content_rule_coredump_disable_backtraces mediumCCE-88825-5

Disable core dump backtraces

Rule IDxccdf_org.ssgproject.content_rule_coredump_disable_backtraces
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-coredump_disable_backtraces:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88825-5

References:
nistCM-6
pcidssReq-3.2
os-srgSRG-OS-000480-GPOS-00227
cis1.5.3
pcidss43.3.1.1, 3.3.1, 3.3
Description
The ProcessSizeMax option in [Coredump] section of /etc/systemd/coredump.conf specifies the maximum size in bytes of a core which will be processed. Core dumps exceeding this size may be stored, but the backtrace will not be generated.
Rationale
A core dump includes a memory image taken at the time the operating system terminates an application. The memory image could contain sensitive data and is generally useful only for developers or system operators trying to debug problems. Enabling core dumps on production systems is not recommended, however there may be overriding operational requirements to enable advanced debuging. Permitting temporary enablement of core dumps during such situations should be reviewed through local needs and policy.
Warnings
warning  If the /etc/systemd/coredump.conf file does not already contain the [Coredump] section, the value will not be configured correctly.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q systemd; then

found=false

# set value in all files if they contain section or key
for f in $(echo -n "/etc/systemd/coredump.conf"); do
    if [ ! -e "$f" ]; then
        continue
    fi

    # find key in section and change value
    if grep -qzosP "[[:space:]]*\[Coredump\]([^\n\[]*\n+)+?[[:space:]]*ProcessSizeMax" "$f"; then

            sed -i "s/ProcessSizeMax[^(\n)]*/ProcessSizeMax=0/" "$f"

            found=true

    # find section and add key = value to it
    elif grep -qs "[[:space:]]*\[Coredump\]" "$f"; then

            sed -i "/[[:space:]]*\[Coredump\]/a ProcessSizeMax=0" "$f"

            found=true
    fi
done

# if section not in any file, append section with key = value to FIRST file in files parameter
if ! $found ; then
    file=$(echo "/etc/systemd/coredump.conf" | cut -f1 -d ' ')
    mkdir -p "$(dirname "$file")"

    echo -e "[Coredump]\nProcessSizeMax=0" >> "$file"

fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88825-5
  - NIST-800-53-CM-6
  - PCI-DSS-Req-3.2
  - PCI-DSSv4-3.3
  - PCI-DSSv4-3.3.1
  - PCI-DSSv4-3.3.1.1
  - coredump_disable_backtraces
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set 'ProcessSizeMax' to '0' in the [Coredump] section of '/etc/systemd/coredump.conf'
  ini_file:
    path: /etc/systemd/coredump.conf
    section: Coredump
    option: ProcessSizeMax
    value: '0'
    create: true
    mode: 420
  when: '"systemd" in ansible_facts.packages'
  tags:
  - CCE-88825-5
  - NIST-800-53-CM-6
  - PCI-DSS-Req-3.2
  - PCI-DSSv4-3.3
  - PCI-DSSv4-3.3.1
  - PCI-DSSv4-3.3.1.1
  - coredump_disable_backtraces
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,%23%20%20This%20file%20is%20part%20of%20systemd.%0A%23%0A%23%20%20systemd%20is%20free%20software%3B%20you%20can%20redistribute%20it%20and/or%20modify%20it%0A%23%20%20under%20the%20terms%20of%20the%20GNU%20Lesser%20General%20Public%20License%20as%20published%20by%0A%23%20%20the%20Free%20Software%20Foundation%3B%20either%20version%202.1%20of%20the%20License%2C%20or%0A%23%20%20%28at%20your%20option%29%20any%20later%20version.%0A%23%0A%23%20Entries%20in%20this%20file%20show%20the%20compile%20time%20defaults.%0A%23%20You%20can%20change%20settings%20by%20editing%20this%20file.%0A%23%20Defaults%20can%20be%20restored%20by%20simply%20deleting%20this%20file.%0A%23%0A%23%20See%20coredump.conf%285%29%20for%20details.%0A%0A%5BCoredump%5D%0A%23Storage%3Dexternal%0A%23Compress%3Dyes%0A%23ProcessSizeMax%3D2G%0A%23ExternalSizeMax%3D2G%0A%23JournalSizeMax%3D767M%0A%23MaxUse%3D%0A%23KeepFree%3D%0AStorage%3Dnone%0AProcessSizeMax%3D0%0A
        mode: 0644
        path: /etc/systemd/coredump.conf
        overwrite: true
OVAL test results details

tests the value of ProcessSizeMax setting in the /etc/systemd/coredump.conf file  oval:ssg-test_coredump_disable_backtraces:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_coredump_disable_backtraces:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/systemd/coredump.conf^\s*\[Coredump\].*(?:\n\s*[^[\s].*)*\n^[ \t]*(?i)ProcessSizeMax(?-i)[ \t]*=[ \t]*(.+?)[ \t]*(?:$|#)1

tests the value of ProcessSizeMax setting in the /etc/systemd/coredump.conf.d file  oval:ssg-test_coredump_disable_backtraces_config_dir:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_coredump_disable_backtraces_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/systemd/coredump.conf.d.*\.conf$^\s*\[Coredump\].*(?:\n\s*[^[\s].*)*\n^[ \t]*(?i)ProcessSizeMax(?-i)[ \t]*=[ \t]*(.+?)[ \t]*(?:$|#)1
Disable storing core dumpxccdf_org.ssgproject.content_rule_coredump_disable_storage mediumCCE-88732-3

Disable storing core dump

Rule IDxccdf_org.ssgproject.content_rule_coredump_disable_storage
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-coredump_disable_storage:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88732-3

References:
nistCM-6
pcidssReq-3.2
os-srgSRG-OS-000480-GPOS-00227
cis1.5.4
pcidss43.3.1.1, 3.3.1, 3.3
Description
The Storage option in [Coredump] sectionof /etc/systemd/coredump.conf can be set to none to disable storing core dumps permanently.
Rationale
A core dump includes a memory image taken at the time the operating system terminates an application. The memory image could contain sensitive data and is generally useful only for developers or system operators trying to debug problems. Enabling core dumps on production systems is not recommended, however there may be overriding operational requirements to enable advanced debuging. Permitting temporary enablement of core dumps during such situations should be reviewed through local needs and policy.
Warnings
warning  If the /etc/systemd/coredump.conf file does not already contain the [Coredump] section, the value will not be configured correctly.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q systemd; then

found=false

# set value in all files if they contain section or key
for f in $(echo -n "/etc/systemd/coredump.conf"); do
    if [ ! -e "$f" ]; then
        continue
    fi

    # find key in section and change value
    if grep -qzosP "[[:space:]]*\[Coredump\]([^\n\[]*\n+)+?[[:space:]]*Storage" "$f"; then

            sed -i "s/Storage[^(\n)]*/Storage=none/" "$f"

            found=true

    # find section and add key = value to it
    elif grep -qs "[[:space:]]*\[Coredump\]" "$f"; then

            sed -i "/[[:space:]]*\[Coredump\]/a Storage=none" "$f"

            found=true
    fi
done

# if section not in any file, append section with key = value to FIRST file in files parameter
if ! $found ; then
    file=$(echo "/etc/systemd/coredump.conf" | cut -f1 -d ' ')
    mkdir -p "$(dirname "$file")"

    echo -e "[Coredump]\nStorage=none" >> "$file"

fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88732-3
  - NIST-800-53-CM-6
  - PCI-DSS-Req-3.2
  - PCI-DSSv4-3.3
  - PCI-DSSv4-3.3.1
  - PCI-DSSv4-3.3.1.1
  - coredump_disable_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set 'Storage' to 'none' in the [Coredump] section of '/etc/systemd/coredump.conf'
  ini_file:
    path: /etc/systemd/coredump.conf
    section: Coredump
    option: Storage
    value: none
    create: true
    mode: 420
  when: '"systemd" in ansible_facts.packages'
  tags:
  - CCE-88732-3
  - NIST-800-53-CM-6
  - PCI-DSS-Req-3.2
  - PCI-DSSv4-3.3
  - PCI-DSSv4-3.3.1
  - PCI-DSSv4-3.3.1.1
  - coredump_disable_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,%23%20%20This%20file%20is%20part%20of%20systemd.%0A%23%0A%23%20%20systemd%20is%20free%20software%3B%20you%20can%20redistribute%20it%20and/or%20modify%20it%0A%23%20%20under%20the%20terms%20of%20the%20GNU%20Lesser%20General%20Public%20License%20as%20published%20by%0A%23%20%20the%20Free%20Software%20Foundation%3B%20either%20version%202.1%20of%20the%20License%2C%20or%0A%23%20%20%28at%20your%20option%29%20any%20later%20version.%0A%23%0A%23%20Entries%20in%20this%20file%20show%20the%20compile%20time%20defaults.%0A%23%20You%20can%20change%20settings%20by%20editing%20this%20file.%0A%23%20Defaults%20can%20be%20restored%20by%20simply%20deleting%20this%20file.%0A%23%0A%23%20See%20coredump.conf%285%29%20for%20details.%0A%0A%5BCoredump%5D%0A%23Storage%3Dexternal%0A%23Compress%3Dyes%0A%23ProcessSizeMax%3D2G%0A%23ExternalSizeMax%3D2G%0A%23JournalSizeMax%3D767M%0A%23MaxUse%3D%0A%23KeepFree%3D%0AStorage%3Dnone%0AProcessSizeMax%3D0%0A
        mode: 0644
        path: /etc/systemd/coredump.conf
        overwrite: true
OVAL test results details

tests the value of Storage setting in the /etc/systemd/coredump.conf file  oval:ssg-test_coredump_disable_storage:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_coredump_disable_storage:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/systemd/coredump.conf^\s*\[Coredump\].*(?:\n\s*[^[\s].*)*\n^[ \t]*(?i)Storage(?-i)[ \t]*=[ \t]*(.+?)[ \t]*(?:$|#)1

tests the value of Storage setting in the /etc/systemd/coredump.conf.d file  oval:ssg-test_coredump_disable_storage_config_dir:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_coredump_disable_storage_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/systemd/coredump.conf.d.*\.conf$^\s*\[Coredump\].*(?:\n\s*[^[\s].*)*\n^[ \t]*(?i)Storage(?-i)[ \t]*=[ \t]*(.+?)[ \t]*(?:$|#)1
Enable Randomized Layout of Virtual Address Spacexccdf_org.ssgproject.content_rule_sysctl_kernel_randomize_va_space mediumCCE-87876-9

Enable Randomized Layout of Virtual Address Space

Rule IDxccdf_org.ssgproject.content_rule_sysctl_kernel_randomize_va_space
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_kernel_randomize_va_space:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-87876-9

References:
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e)
nerc-cipCIP-002-5 R1.1, CIP-002-5 R1.2, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 4.1, CIP-004-6 4.2, CIP-004-6 R2.2.3, CIP-004-6 R2.2.4, CIP-004-6 R2.3, CIP-004-6 R4, CIP-005-6 R1, CIP-005-6 R1.1, CIP-005-6 R1.2, CIP-007-3 R3, CIP-007-3 R3.1, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, CIP-007-3 R8.4, CIP-009-6 R.1.1, CIP-009-6 R4
nistSC-30, SC-30(2), CM-6(a)
pcidssReq-2.2.1
os-srgSRG-OS-000433-GPOS-00193, SRG-OS-000480-GPOS-00227
app-srg-ctrSRG-APP-000450-CTR-001105
anssiR9
cis1.5.1
pcidss43.3.1.1, 3.3.1, 3.3
Description
To set the runtime status of the kernel.randomize_va_space kernel parameter, run the following command:
$ sudo sysctl -w kernel.randomize_va_space=2
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
kernel.randomize_va_space = 2
Rationale
Address space layout randomization (ASLR) makes it more difficult for an attacker to predict the location of attack code they have introduced into a process's address space during an attempt at exploitation. Additionally, ASLR makes it more difficult for an attacker to know the location of existing code in order to re-purpose it using return oriented programming (ROP) techniques.

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of kernel.randomize_va_space from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*kernel.randomize_va_space.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "kernel.randomize_va_space" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"


#
# Set runtime for kernel.randomize_va_space
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w kernel.randomize_va_space="2"
fi

#
# If kernel.randomize_va_space present in /etc/sysctl.conf, change value to "2"
#	else, add "kernel.randomize_va_space = 2" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^kernel.randomize_va_space")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "2"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^kernel.randomize_va_space\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^kernel.randomize_va_space\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-87876-9"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87876-9
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-30
  - NIST-800-53-SC-30(2)
  - PCI-DSS-Req-2.2.1
  - PCI-DSSv4-3.3
  - PCI-DSSv4-3.3.1
  - PCI-DSSv4-3.3.1.1
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_kernel_randomize_va_space

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*kernel.randomize_va_space.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87876-9
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-30
  - NIST-800-53-SC-30(2)
  - PCI-DSS-Req-2.2.1
  - PCI-DSSv4-3.3
  - PCI-DSSv4-3.3.1
  - PCI-DSSv4-3.3.1.1
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_kernel_randomize_va_space

- name: Comment out any occurrences of kernel.randomize_va_space from config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*kernel.randomize_va_space
    replace: '#kernel.randomize_va_space'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87876-9
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-30
  - NIST-800-53-SC-30(2)
  - PCI-DSS-Req-2.2.1
  - PCI-DSSv4-3.3
  - PCI-DSSv4-3.3.1
  - PCI-DSSv4-3.3.1.1
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_kernel_randomize_va_space

- name: Ensure sysctl kernel.randomize_va_space is set to 2
  sysctl:
    name: kernel.randomize_va_space
    value: '2'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87876-9
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-30
  - NIST-800-53-SC-30(2)
  - PCI-DSS-Req-2.2.1
  - PCI-DSSv4-3.3
  - PCI-DSSv4-3.3.1
  - PCI-DSSv4-3.3.1.1
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_kernel_randomize_va_space

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,kernel.randomize_va_space%3D2%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_kernel_randomize_va_space.conf
        overwrite: true
OVAL test results details

kernel.randomize_va_space static configuration  oval:ssg-test_sysctl_kernel_randomize_va_space_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_kernel_randomize_va_space:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_kernel_randomize_va_space:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_kernel_randomize_va_space:obj:1

kernel.randomize_va_space static configuration  oval:ssg-test_sysctl_kernel_randomize_va_space_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_kernel_randomize_va_space:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_kernel_randomize_va_space:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_kernel_randomize_va_space:obj:1

kernel.randomize_va_space static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_kernel_randomize_va_space_static_pkg_correct:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_usr_lib_sysctld_sysctl_kernel_randomize_va_space:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/sysctl.d^.*\.conf$^[\s]*kernel.randomize_va_space[\s]*=[\s]*(.*\S)[\s]*$1

kernel runtime parameter kernel.randomize_va_space set to 2  oval:ssg-test_sysctl_kernel_randomize_va_space_runtime:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameValue
truekernel.randomize_va_space2
Restrict usage of ptrace to descendant processesxccdf_org.ssgproject.content_rule_sysctl_kernel_yama_ptrace_scope mediumCCE-88785-1

Restrict usage of ptrace to descendant processes

Rule IDxccdf_org.ssgproject.content_rule_sysctl_kernel_yama_ptrace_scope
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sysctl_kernel_yama_ptrace_scope:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88785-1

References:
nistSC-7(10)
osppFMT_SMF_EXT.1
os-srgSRG-OS-000132-GPOS-00067, SRG-OS-000480-GPOS-00227
anssiR11
cis1.5.2
Description
To set the runtime status of the kernel.yama.ptrace_scope kernel parameter, run the following command:
$ sudo sysctl -w kernel.yama.ptrace_scope=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d:
kernel.yama.ptrace_scope = 1
Rationale
Unrestricted usage of ptrace allows compromised binaries to run ptrace on another processes of the user. Like this, the attacker can steal sensitive information from the target processes (e.g. SSH sessions, web browser, ...) without any additional assistance from the user (i.e. without resorting to phishing).

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

# Comment out any occurrences of kernel.yama.ptrace_scope from /etc/sysctl.d/*.conf files

for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do


  # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
  if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

  matching_list=$(grep -P '^(?!#).*[\s]*kernel.yama.ptrace_scope.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
      # comment out "kernel.yama.ptrace_scope" matches to preserve user data
      sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
    done <<< "$matching_list"
  fi
done

#
# Set sysctl config file which to save the desired value
#

SYSCONFIG_FILE="/etc/sysctl.conf"


#
# Set runtime for kernel.yama.ptrace_scope
#
if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    /sbin/sysctl -q -n -w kernel.yama.ptrace_scope="1"
fi

#
# If kernel.yama.ptrace_scope present in /etc/sysctl.conf, change value to "1"
#	else, add "kernel.yama.ptrace_scope = 1" to /etc/sysctl.conf
#

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^kernel.yama.ptrace_scope")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "1"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^kernel.yama.ptrace_scope\\>" "${SYSCONFIG_FILE}"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^kernel.yama.ptrace_scope\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
    if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
    fi
    cce="CCE-88785-1"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
    printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88785-1
  - NIST-800-53-SC-7(10)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_kernel_yama_ptrace_scope

- name: List /etc/sysctl.d/*.conf files
  find:
    paths:
    - /etc/sysctl.d/
    - /run/sysctl.d/
    - /usr/local/lib/sysctl.d/
    contains: ^[\s]*kernel.yama.ptrace_scope.*$
    patterns: '*.conf'
    file_type: any
  register: find_sysctl_d
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88785-1
  - NIST-800-53-SC-7(10)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_kernel_yama_ptrace_scope

- name: Comment out any occurrences of kernel.yama.ptrace_scope from config files
  replace:
    path: '{{ item.path }}'
    regexp: ^[\s]*kernel.yama.ptrace_scope
    replace: '#kernel.yama.ptrace_scope'
  loop: '{{ find_sysctl_d.files }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88785-1
  - NIST-800-53-SC-7(10)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_kernel_yama_ptrace_scope

- name: Ensure sysctl kernel.yama.ptrace_scope is set to 1
  sysctl:
    name: kernel.yama.ptrace_scope
    value: '1'
    sysctl_file: /etc/sysctl.conf
    state: present
    reload: true
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88785-1
  - NIST-800-53-SC-7(10)
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required
  - sysctl_kernel_yama_ptrace_scope

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,kernel.yama.ptrace_scope%3D1%0A
        mode: 0644
        path: /etc/sysctl.d/75-sysctl_kernel_yama_ptrace_scope.conf
        overwrite: true
OVAL test results details

kernel.yama.ptrace_scope static configuration  oval:ssg-test_sysctl_kernel_yama_ptrace_scope_static_user:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_kernel_yama_ptrace_scope:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_kernel_yama_ptrace_scope:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_kernel_yama_ptrace_scope:obj:1

kernel.yama.ptrace_scope static configuration  oval:ssg-test_sysctl_kernel_yama_ptrace_scope_static_user_missing:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_static_user_sysctl_kernel_yama_ptrace_scope:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_static_etc_lib_sysctls_sysctl_kernel_yama_ptrace_scope:obj:1 oval:ssg-object_static_run_usr_local_sysctls_sysctl_kernel_yama_ptrace_scope:obj:1

kernel.yama.ptrace_scope static configuration in /usr/lib/sysctl.d/*.conf  oval:ssg-test_sysctl_kernel_yama_ptrace_scope_static_pkg_correct:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/usr/lib/sysctl.d/10-default-yama-scope.confkernel.yama.ptrace_scope = 0

kernel runtime parameter kernel.yama.ptrace_scope set to 1  oval:ssg-test_sysctl_kernel_yama_ptrace_scope_runtime:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameValue
falsekernel.yama.ptrace_scope0
Install libselinux Packagexccdf_org.ssgproject.content_rule_package_libselinux_installed highCCE-90410-2

Install libselinux Package

Rule IDxccdf_org.ssgproject.content_rule_package_libselinux_installed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_libselinux_installed:def:1
Time2025-07-22T08:31:56+00:00
Severityhigh
Identifiers:

CCE-90410-2

References:
cis1.3.1.1
pcidss41.2.6, 1.2
Description
The libselinux package can be installed with the following command:
$ sudo dnf install libselinux
Rationale
Security-enhanced Linux is a feature of the Linux kernel and a number of utilities with enhanced security functionality designed to add mandatory access controls to Linux. The libselinux package contains the core library of the Security-enhanced Linux system.
OVAL test results details

package libselinux is installed  oval:ssg-test_package_libselinux_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedlibselinuxx86_64(none)2.el10_03.80:3.8-2.el10_0199e2f91fd431d51libselinux-0:3.8-2.el10_0.x86_64
Uninstall mcstrans Packagexccdf_org.ssgproject.content_rule_package_mcstrans_removed lowCCE-88183-9

Uninstall mcstrans Package

Rule IDxccdf_org.ssgproject.content_rule_package_mcstrans_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_mcstrans_removed:def:1
Time2025-07-22T08:31:56+00:00
Severitylow
Identifiers:

CCE-88183-9

References:
cis1.3.1.7
Description
The mcstransd daemon provides category label information to client processes requesting information. The label translations are defined in /etc/selinux/targeted/setrans.conf. The mcstrans package can be removed with the following command:
$ sudo dnf remove mcstrans
Rationale
Since this service is not used very often, disable it to reduce the amount of potentially vulnerable code running on the system.
OVAL test results details

package mcstrans is removed  oval:ssg-test_package_mcstrans_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_mcstrans_removed:obj:1 of type rpminfo_object
Name
mcstrans
Uninstall setroubleshoot Packagexccdf_org.ssgproject.content_rule_package_setroubleshoot_removed lowCCE-89902-1

Uninstall setroubleshoot Package

Rule IDxccdf_org.ssgproject.content_rule_package_setroubleshoot_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_setroubleshoot_removed:def:1
Time2025-07-22T08:31:56+00:00
Severitylow
Identifiers:

CCE-89902-1

References:
anssiR49
cis1.3.1.8
Description
The SETroubleshoot service notifies desktop users of SELinux denials. The service provides information around configuration errors, unauthorized intrusions, and other potential errors. The setroubleshoot package can be removed with the following command:
$ sudo dnf remove setroubleshoot
Rationale
The SETroubleshoot service is an unnecessary daemon to have running on a server, especially if X Windows is removed or disabled.
OVAL test results details

package setroubleshoot is removed  oval:ssg-test_package_setroubleshoot_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_setroubleshoot_removed:obj:1 of type rpminfo_object
Name
setroubleshoot
Ensure SELinux Not Disabled in /etc/default/grubxccdf_org.ssgproject.content_rule_grub2_enable_selinux mediumCCE-88989-9

Ensure SELinux Not Disabled in /etc/default/grub

Rule IDxccdf_org.ssgproject.content_rule_grub2_enable_selinux
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-grub2_enable_selinux:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88989-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 3, 4, 5, 6, 8, 9
cobit5APO01.06, APO11.04, APO13.01, BAI03.05, DSS01.05, DSS03.01, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06, MEA02.01
cui3.1.2, 3.7.2
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e)
isa-62443-20094.2.3.4, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistAC-3, AC-3(3)(a)
nist-csfDE.AE-1, ID.AM-3, PR.AC-4, PR.AC-5, PR.AC-6, PR.DS-5, PR.PT-1, PR.PT-3, PR.PT-4
cis1.3.1.2
pcidss41.2.6, 1.2
Description
SELinux can be disabled at boot time by an argument in /etc/default/grub. Remove any instances of selinux=0 from the kernel arguments in that file to prevent SELinux from being disabled at boot.
Rationale
Disabling a major host protection feature, such as SELinux, at boot time prevents it from confining system services at boot time. Further, it increases the chances that it will remain off during system operation.
OVAL test results details

check value selinux|enforcing=0 in /etc/default/grub, fail if found  oval:ssg-test_selinux_default_grub:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_selinux_default_grub:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/default/grub^[\s]*GRUB_CMDLINE_LINUX.*(selinux|enforcing)=0.*$1

check value selinux|enforcing=0 in /etc/grub2.cfg, fail if found  oval:ssg-test_selinux_grub2_cfg:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_selinux_grub2_cfg:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/grub2.cfg^.*(selinux|enforcing)=0.*$1

check value selinux|enforcing=0 in /etc/grub.d fail if found  oval:ssg-test_selinux_grub_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_selinux_grub_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/grub.d^.*$^.*(selinux|enforcing)=0.*$1
Ensure SELinux is Not Disabledxccdf_org.ssgproject.content_rule_selinux_not_disabled highCCE-90205-6

Ensure SELinux is Not Disabled

Rule IDxccdf_org.ssgproject.content_rule_selinux_not_disabled
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-selinux_not_disabled:def:1
Time2025-07-22T08:31:56+00:00
Severityhigh
Identifiers:

CCE-90205-6

References:
cis1.3.1.4
Description
The SELinux state should be set to enforcing or permissive at system boot time. In the file /etc/selinux/config, add or correct the following line to configure the system to boot into enforcing or permissive mode:
SELINUX=enforcing
OR
SELINUX=permissive
Rationale
Running SELinux in disabled mode is strongly discouraged. It prevents enforcing the SELinux controls without a system reboot. It also avoids labeling any persistent objects such as files, making it difficult to enable SELinux in the future.
Warnings
warning  In case the SELinux is "disabled", the automated remediation will adopt a more conservative approach and set it to "permissive" in order to avoid any system disruption and give the administrator the opportunity to assess the impact and necessary efforts before setting it to "enforcing", which is strongly recommended.
OVAL test results details

SELinux is not disabled in /etc/selinux/config  oval:ssg-test_selinux_not_disabled:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/selinux/configSELINUX=enforcing
Configure SELinux Policyxccdf_org.ssgproject.content_rule_selinux_policytype mediumCCE-88366-0

Configure SELinux Policy

Rule IDxccdf_org.ssgproject.content_rule_selinux_policytype
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-selinux_policytype:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-88366-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 3, 4, 5, 6, 8, 9
cobit5APO01.06, APO11.04, APO13.01, BAI03.05, DSS01.05, DSS03.01, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06, MEA02.01
cui3.1.2, 3.7.2
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e)
isa-62443-20094.2.3.4, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.2, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-004-6 R3.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, CIP-007-3 R6.5
nistAC-3, AC-3(3)(a), AU-9, SC-7(21)
nist-csfDE.AE-1, ID.AM-3, PR.AC-4, PR.AC-5, PR.AC-6, PR.DS-5, PR.PT-1, PR.PT-3, PR.PT-4
osppFMT_MOF_EXT.1
os-srgSRG-OS-000445-GPOS-00199
app-srg-ctrSRG-APP-000233-CTR-000585
anssiR46, R64
bsiAPP.4.4.A4, SYS.1.6.A3, SYS.1.6.A18, SYS.1.6.A21
cis1.3.1.3
pcidss41.2.6, 1.2
Description
The SELinux targeted policy is appropriate for general-purpose desktops and servers, as well as systems in many other roles. To configure the system to use this policy, add or correct the following line in /etc/selinux/config:
SELINUXTYPE=targeted
       
Other policies, such as mls, provide additional security labeling and greater confinement but are not compatible with many general-purpose use cases.
Rationale
Setting the SELinux policy to targeted or a more specialized policy ensures the system will confine processes that are likely to be targeted for exploitation, such as network or system services.

Note: During the development or debugging of SELinux modules, it is common to temporarily place non-production systems in permissive mode. In such temporary cases, SELinux policies should be developed, and once work is completed, the system should be reconfigured to targeted.
OVAL test results details

tests the value of SELINUXTYPE setting in the /etc/selinux/config file  oval:ssg-test_selinux_policytype:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/selinux/configSELINUXTYPE=targeted

The configuration file /etc/selinux/config exists for selinux_policytype  oval:ssg-test_selinux_policytype_config_file_exists:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/etc/selinux/configregular001187rw-r--r-- 
Ensure SELinux State is Enforcingxccdf_org.ssgproject.content_rule_selinux_state highCCE-89386-7

Ensure SELinux State is Enforcing

Rule IDxccdf_org.ssgproject.content_rule_selinux_state
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-selinux_state:def:1
Time2025-07-22T08:31:56+00:00
Severityhigh
Identifiers:

CCE-89386-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 3, 4, 5, 6, 8, 9
cobit5APO01.06, APO11.04, APO13.01, BAI03.05, DSS01.05, DSS03.01, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06, MEA02.01
cui3.1.2, 3.7.2
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e)
isa-62443-20094.2.3.4, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.2, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-004-6 R3.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, CIP-007-3 R6.5
nistAC-3, AC-3(3)(a), AU-9, SC-7(21)
nist-csfDE.AE-1, ID.AM-3, PR.AC-4, PR.AC-5, PR.AC-6, PR.DS-5, PR.PT-1, PR.PT-3, PR.PT-4
osppFMT_MOF_EXT.1
os-srgSRG-OS-000445-GPOS-00199, SRG-OS-000134-GPOS-00068
anssiR37, R79
bsiAPP.4.4.A4, SYS.1.6.A3, SYS.1.6.A18, SYS.1.6.A21
cis1.3.1.5
pcidss41.2.6, 1.2
Description
The SELinux state should be set to enforcing at system boot time. In the file /etc/selinux/config, add or correct the following line to configure the system to boot into enforcing mode:
SELINUX=enforcing
       
Rationale
Setting the SELinux state to enforcing ensures SELinux is able to confine potentially compromised processes to the security policy, which is designed to prevent them from causing damage to the system or further elevating their privileges.
OVAL test results details

/selinux/enforce is 1  oval:ssg-test_etc_selinux_config:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/selinux/configSELINUX=enforcing
Disable Avahi Server Softwarexccdf_org.ssgproject.content_rule_service_avahi-daemon_disabled mediumCCE-90062-1

Disable Avahi Server Software

Rule IDxccdf_org.ssgproject.content_rule_service_avahi-daemon_disabled
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-90062-1

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
cis2.1.2
pcidss42.2.4, 2.2
Description
The avahi-daemon service can be disabled with the following command:
$ sudo systemctl mask --now avahi-daemon.service
Rationale
Because the Avahi daemon service keeps an open network port, it is subject to network attacks. Its functionality is convenient but is only appropriate if the local network can be trusted.
Ensure that /etc/at.deny does not existxccdf_org.ssgproject.content_rule_file_at_deny_not_exist mediumCCE-89507-8

Ensure that /etc/at.deny does not exist

Rule IDxccdf_org.ssgproject.content_rule_file_at_deny_not_exist
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_at_deny_not_exist:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-89507-8

References:
cis2.4.2.1
pcidss42.2.6, 2.2
Description
The file /etc/at.deny should not exist. Use /etc/at.allow instead.
Rationale
Access to at should be restricted. It is easier to manage an allow list than a deny list.
OVAL test results details

Test that that /etc/at.deny does not exist  oval:ssg-test_file_at_deny_not_exist:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_at_deny_not_exist:obj:1 of type file_object
Filepath
/etc/at.deny
Ensure that /etc/cron.allow existsxccdf_org.ssgproject.content_rule_file_cron_allow_exists mediumCCE-87235-8

Ensure that /etc/cron.allow exists

Rule IDxccdf_org.ssgproject.content_rule_file_cron_allow_exists
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_cron_allow_exists:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-87235-8

References:
cis2.4.1.8
Description
The file /etc/cron.allow should exist and should be used instead of /etc/cron.deny.
Rationale
Access to crontab should be restricted. It is easier to manage an allow list than a deny list. Therefore, /etc/cron.allow needs to be created and used instead of /etc/cron.deny. Regardless of the existence of any of these files, the root administrative user is always allowed to setup a crontab.

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

touch /etc/cron.allow
    chown 0 /etc/cron.allow
    chmod 0600 /etc/cron.allow

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87235-8
  - disable_strategy
  - file_cron_allow_exists
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Add empty /etc/cron.allow
  file:
    path: /etc/cron.allow
    state: touch
    owner: '0'
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87235-8
  - disable_strategy
  - file_cron_allow_exists
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Test that that /etc/cron.allow does exist  oval:ssg-test_file_cron_allow_exists:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_file_cron_allow_exists:obj:1 of type file_object
Filepath
/etc/cron.allow
Ensure that /etc/cron.deny does not existxccdf_org.ssgproject.content_rule_file_cron_deny_not_exist mediumCCE-88579-8

Ensure that /etc/cron.deny does not exist

Rule IDxccdf_org.ssgproject.content_rule_file_cron_deny_not_exist
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_cron_deny_not_exist:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88579-8

References:
cis2.4.1.8
pcidss42.2.6, 2.2
Description
The file /etc/cron.deny should not exist. Use /etc/cron.allow instead.
Rationale
Access to cron should be restricted. It is easier to manage an allow list than a deny list.

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

if [[ -f  /etc/cron.deny ]]; then
        rm /etc/cron.deny
    fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88579-8
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - disable_strategy
  - file_cron_deny_not_exist
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Remove /etc/cron.deny
  file:
    path: /etc/cron.deny
    state: absent
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88579-8
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - disable_strategy
  - file_cron_deny_not_exist
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Test that that /etc/cron.deny does not exist  oval:ssg-test_file_cron_deny_not_exist:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/etc/cron.denyregular000rw-r--r-- 
Verify Group Who Owns /etc/at.allow filexccdf_org.ssgproject.content_rule_file_groupowner_at_allow mediumCCE-90598-4

Verify Group Who Owns /etc/at.allow file

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_at_allow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_at_allow:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-90598-4

References:
cis2.4.2.1
pcidss42.2.6, 2.2
Description
If /etc/at.allow exists, it must be group-owned by root. To properly set the group owner of /etc/at.allow, run the command:
$ sudo chgrp root /etc/at.allow
Rationale
If the owner of the at.allow file is not set to root, the possibility exists for an unauthorized user to view or edit sensitive information.
OVAL test results details

Testing group ownership of /etc/at.allow  oval:ssg-test_file_groupowner_at_allow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_at_allow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/at.allowoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_at_allow_0_0:ste:1
Verify Group Who Owns /etc/cron.allow filexccdf_org.ssgproject.content_rule_file_groupowner_cron_allow mediumCCE-90094-4

Verify Group Who Owns /etc/cron.allow file

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_cron_allow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_cron_allow:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-90094-4

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.8
pcidss42.2.6, 2.2
Description
If /etc/cron.allow exists, it must be group-owned by root. To properly set the group owner of /etc/cron.allow, run the command:
$ sudo chgrp root /etc/cron.allow
Rationale
If the owner of the cron.allow file is not set to root, the possibility exists for an unauthorized user to view or edit sensitive information.
OVAL test results details

Testing group ownership of /etc/cron.allow  oval:ssg-test_file_groupowner_cron_allow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_cron_allow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/cron.allowoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_cron_allow_0_0:ste:1
Verify User Who Owns /etc/at.allow filexccdf_org.ssgproject.content_rule_file_owner_at_allow mediumCCE-88524-4

Verify User Who Owns /etc/at.allow file

Rule IDxccdf_org.ssgproject.content_rule_file_owner_at_allow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_at_allow:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88524-4

References:
cis2.4.2.1
pcidss42.2.6, 2.2
Description
If /etc/at.allow exists, it must be owned by root. To properly set the owner of /etc/at.allow, run the command:
$ sudo chown root /etc/at.allow 
Rationale
If the owner of the at.allow file is not set to root, the possibility exists for an unauthorized user to view or edit sensitive information.
OVAL test results details

Testing user ownership of /etc/at.allow  oval:ssg-test_file_owner_at_allow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_at_allow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/at.allowoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_at_allow_0_0:ste:1
Verify User Who Owns /etc/cron.allow filexccdf_org.ssgproject.content_rule_file_owner_cron_allow mediumCCE-88914-7

Verify User Who Owns /etc/cron.allow file

Rule IDxccdf_org.ssgproject.content_rule_file_owner_cron_allow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_cron_allow:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88914-7

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.8
pcidss42.2.6, 2.2
Description
If /etc/cron.allow exists, it must be owned by root. To properly set the owner of /etc/cron.allow, run the command:
$ sudo chown root /etc/cron.allow 
Rationale
If the owner of the cron.allow file is not set to root, the possibility exists for an unauthorized user to view or edit sensitive information.
OVAL test results details

Testing user ownership of /etc/cron.allow  oval:ssg-test_file_owner_cron_allow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_cron_allow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/cron.allowoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_cron_allow_0_0:ste:1
Verify Permissions on /etc/at.allow filexccdf_org.ssgproject.content_rule_file_permissions_at_allow mediumCCE-90283-3

Verify Permissions on /etc/at.allow file

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_at_allow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_at_allow:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-90283-3

References:
cis2.4.2.1
pcidss42.2.6, 2.2
Description
If /etc/at.allow exists, it must have permissions 0600 or more restrictive. To properly set the permissions of /etc/at.allow, run the command:
$ sudo chmod 0600 /etc/at.allow
Rationale
If the permissions of the at.allow file are not set to 0600 or more restrictive, the possibility exists for an unauthorized user to view or edit sensitive information.
OVAL test results details

Testing mode of /etc/at.allow  oval:ssg-test_file_permissions_at_allow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_at_allow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/at.allowoval:ssg-exclude_symlinks__at_allow:ste:1oval:ssg-state_file_permissions_at_allow_0_mode_0600or_stricter_:ste:1
Verify Permissions on /etc/cron.allow filexccdf_org.ssgproject.content_rule_file_permissions_cron_allow mediumCCE-89121-8

Verify Permissions on /etc/cron.allow file

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_cron_allow
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_cron_allow:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-89121-8

References:
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.8
pcidss42.2.6, 2.2
Description
If /etc/cron.allow exists, it must have permissions 0600 or more restrictive. To properly set the permissions of /etc/cron.allow, run the command:
$ sudo chmod 0600 /etc/cron.allow
Rationale
If the permissions of the cron.allow file are not set to 0600 or more restrictive, the possibility exists for an unauthorized user to view or edit sensitive information.
OVAL test results details

Testing mode of /etc/cron.allow  oval:ssg-test_file_permissions_cron_allow_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_cron_allow_0:obj:1 of type file_object
FilepathFilterFilter
/etc/cron.allowoval:ssg-exclude_symlinks__cron_allow:ste:1oval:ssg-state_file_permissions_cron_allow_0_mode_0600or_stricter_:ste:1
Install the cron servicexccdf_org.ssgproject.content_rule_package_cron_installed mediumCCE-86619-4

Install the cron service

Rule IDxccdf_org.ssgproject.content_rule_package_cron_installed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_cron_installed:def:1
Time2025-07-22T08:31:56+00:00
Severitymedium
Identifiers:

CCE-86619-4

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-6(a)
nist-csfPR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.1
pcidss42.2.6, 2.2
Description
The Cron service should be installed.
Rationale
The cron service allow periodic job execution, needed for almost all administrative tasks and services (software update, log rotating, etc.). Access to cron service should be restricted to administrative accounts only.
OVAL test results details

package cronie is installed  oval:ssg-test_package_cronie_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedcroniex86_64(none)9.el101.7.00:1.7.0-9.el10199e2f91fd431d51cronie-0:1.7.0-9.el10.x86_64
Enable cron Servicexccdf_org.ssgproject.content_rule_service_crond_enabled mediumCCE-90044-9

Enable cron Service

Rule IDxccdf_org.ssgproject.content_rule_service_crond_enabled
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-service_crond_enabled:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-90044-9

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-6(a)
nist-csfPR.IP-1, PR.PT-3
cis2.4.1.1
Description
The crond service is used to execute commands at preconfigured times. It is required by almost all systems to perform necessary maintenance tasks, such as notifying root of system activity. The crond service can be enabled with the following command:
$ sudo systemctl enable crond.service
Rationale
Due to its usage for maintenance and security-supporting tasks, enabling the cron daemon is essential.
OVAL test results details

package cronie is installed  oval:ssg-test_service_crond_package_cronie_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedcroniex86_64(none)9.el101.7.00:1.7.0-9.el10199e2f91fd431d51cronie-0:1.7.0-9.el10.x86_64

Test that the crond service is running  oval:ssg-test_service_running_crond:tst:1  true

Following items have been found on the system:
Result of item-state comparisonUnitPropertyValue
truecrond.serviceActiveStateactive

systemd test  oval:ssg-test_multi_user_wants_crond:tst:1  true

Following items have been found on the system:
Result of item-state comparisonUnitDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependency
truemulti-user.targetbasic.targetsysinit.targetlvm2-lvmpolld.socketswap.targetdev-mqueue.mountsystemd-update-utmp.servicesys-kernel-tracing.mountsystemd-hibernate-clear.servicesystemd-udevd.servicesystemd-update-done.servicesystemd-journal-catalog-update.servicesystemd-ask-password-console.pathsystemd-boot-random-seed.servicesystemd-network-generator.serviceintegritysetup.targetsystemd-confext.servicesystemd-firstboot.servicesystemd-tpm2-setup.servicelvm2-monitor.servicesystemd-tmpfiles-setup-dev-early.servicesystemd-sysusers.serviceproc-sys-fs-binfmt_misc.automountldconfig.servicesystemd-machine-id-commit.servicesystemd-repart.servicecryptsetup.targetfips-crypto-policy-overlay.servicesystemd-pstore.serviceselinux-autorelabel-mark.servicesystemd-random-seed.servicedracut-shutdown.servicesystemd-sysext.servicesys-kernel-debug.mountsystemd-journald.servicedev-hugepages.mountveritysetup.targetsys-fs-fuse-connections.mountsystemd-journal-flush.servicesystemd-tmpfiles-setup.servicesystemd-modules-load.servicelocal-fs.target-.mountboot-efi.mountsystemd-remount-fs.servicesystemd-udev-trigger.servicesystemd-tpm2-setup-early.servicesystemd-pcrphase-sysinit.servicesystemd-sysctl.servicekmod-static-nodes.servicesystemd-pcrphase.servicesystemd-hwdb-update.servicesystemd-tmpfiles-setup-dev.servicesystemd-binfmt.servicesys-kernel-config.mountsystemd-pcrmachine.servicetimers.targetfstrim.timerdnf-makecache.timerlogrotate.timernm-cloud-setup.timerfwupd-refresh.timersystemd-tmpfiles-clean.timerraid-check.timerslices.target-.slicesystem.slicepaths.targetsockets.targetsystemd-coredump.socketsystemd-userdbd.socketsystemd-journald.socketsystemd-pcrextend.socketsshd-unix-local.socketsystemd-creds.socketsystemd-pcrlock.socketpcscd.socketsystemd-sysext.socketsystemd-udevd-kernel.socketsssd-kcm.socketdbus.socketsshd-vsock.socketsystemd-hostnamed.socketsystemd-bootctl.socketsystemd-udevd-control.socketsystemd-journald-dev-log.socketdm-event.socketsystemd-initctl.socketsystemd-logind.serviceinsights-unregister.pathsshd.servicecloud-init.targetcloud-config.servicecloud-init.servicecloud-final.servicecloud-init-local.servicesssd.serviceaudit-rules.servicerhcd.pathremote-fs.targetinsights-client-boot.servicerhsmcertd.serviceinsights-unregistered.pathsystemd-user-sessions.servicetuned.serviceremote-cryptsetup.targetinsights-register.pathrhcd-stop.pathirqbalance.servicesystemd-ask-password-wall.pathrsyslog.servicegetty.targetgetty@tty1.serviceserial-getty@ttyS0.servicecrond.servicekdump.serviceauditd.serviceNetworkManager.servicemdmonitor.servicesystemd-update-utmp-runlevel.servicechronyd.service

systemd test  oval:ssg-test_multi_user_wants_crond_socket:tst:1  false

Following items have been found on the system:
Result of item-state comparisonUnitDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependency
falsemulti-user.targetbasic.targetsysinit.targetlvm2-lvmpolld.socketswap.targetdev-mqueue.mountsystemd-update-utmp.servicesys-kernel-tracing.mountsystemd-hibernate-clear.servicesystemd-udevd.servicesystemd-update-done.servicesystemd-journal-catalog-update.servicesystemd-ask-password-console.pathsystemd-boot-random-seed.servicesystemd-network-generator.serviceintegritysetup.targetsystemd-confext.servicesystemd-firstboot.servicesystemd-tpm2-setup.servicelvm2-monitor.servicesystemd-tmpfiles-setup-dev-early.servicesystemd-sysusers.serviceproc-sys-fs-binfmt_misc.automountldconfig.servicesystemd-machine-id-commit.servicesystemd-repart.servicecryptsetup.targetfips-crypto-policy-overlay.servicesystemd-pstore.serviceselinux-autorelabel-mark.servicesystemd-random-seed.servicedracut-shutdown.servicesystemd-sysext.servicesys-kernel-debug.mountsystemd-journald.servicedev-hugepages.mountveritysetup.targetsys-fs-fuse-connections.mountsystemd-journal-flush.servicesystemd-tmpfiles-setup.servicesystemd-modules-load.servicelocal-fs.target-.mountboot-efi.mountsystemd-remount-fs.servicesystemd-udev-trigger.servicesystemd-tpm2-setup-early.servicesystemd-pcrphase-sysinit.servicesystemd-sysctl.servicekmod-static-nodes.servicesystemd-pcrphase.servicesystemd-hwdb-update.servicesystemd-tmpfiles-setup-dev.servicesystemd-binfmt.servicesys-kernel-config.mountsystemd-pcrmachine.servicetimers.targetfstrim.timerdnf-makecache.timerlogrotate.timernm-cloud-setup.timerfwupd-refresh.timersystemd-tmpfiles-clean.timerraid-check.timerslices.target-.slicesystem.slicepaths.targetsockets.targetsystemd-coredump.socketsystemd-userdbd.socketsystemd-journald.socketsystemd-pcrextend.socketsshd-unix-local.socketsystemd-creds.socketsystemd-pcrlock.socketpcscd.socketsystemd-sysext.socketsystemd-udevd-kernel.socketsssd-kcm.socketdbus.socketsshd-vsock.socketsystemd-hostnamed.socketsystemd-bootctl.socketsystemd-udevd-control.socketsystemd-journald-dev-log.socketdm-event.socketsystemd-initctl.socketsystemd-logind.serviceinsights-unregister.pathsshd.servicecloud-init.targetcloud-config.servicecloud-init.servicecloud-final.servicecloud-init-local.servicesssd.serviceaudit-rules.servicerhcd.pathremote-fs.targetinsights-client-boot.servicerhsmcertd.serviceinsights-unregistered.pathsystemd-user-sessions.servicetuned.serviceremote-cryptsetup.targetinsights-register.pathrhcd-stop.pathirqbalance.servicesystemd-ask-password-wall.pathrsyslog.servicegetty.targetgetty@tty1.serviceserial-getty@ttyS0.servicecrond.servicekdump.serviceauditd.serviceNetworkManager.servicemdmonitor.servicesystemd-update-utmp-runlevel.servicechronyd.service
Verify Group Who Owns cron.dxccdf_org.ssgproject.content_rule_file_groupowner_cron_d mediumCCE-89321-4

Verify Group Who Owns cron.d

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_cron_d
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_cron_d:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-89321-4

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.7
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/cron.d, run the command:
$ sudo chgrp root /etc/cron.d
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
OVAL test results details

Testing group ownership of /etc/cron.d/  oval:ssg-test_file_groupowner_cron_d_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_cron_d_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.dno valueoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_cron_d_0_0:ste:1
Verify Group Who Owns cron.dailyxccdf_org.ssgproject.content_rule_file_groupowner_cron_daily mediumCCE-90342-7

Verify Group Who Owns cron.daily

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_cron_daily
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_cron_daily:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-90342-7

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.4
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/cron.daily, run the command:
$ sudo chgrp root /etc/cron.daily
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
OVAL test results details

Testing group ownership of /etc/cron.daily/  oval:ssg-test_file_groupowner_cron_daily_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_cron_daily_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.dailyno valueoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_cron_daily_0_0:ste:1
Verify Group Who Owns cron.hourlyxccdf_org.ssgproject.content_rule_file_groupowner_cron_hourly mediumCCE-88140-9

Verify Group Who Owns cron.hourly

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_cron_hourly
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_cron_hourly:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88140-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.3
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/cron.hourly, run the command:
$ sudo chgrp root /etc/cron.hourly
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
OVAL test results details

Testing group ownership of /etc/cron.hourly/  oval:ssg-test_file_groupowner_cron_hourly_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_cron_hourly_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.hourlyno valueoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_cron_hourly_0_0:ste:1
Verify Group Who Owns cron.monthlyxccdf_org.ssgproject.content_rule_file_groupowner_cron_monthly mediumCCE-88986-5

Verify Group Who Owns cron.monthly

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_cron_monthly
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_cron_monthly:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88986-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.6
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/cron.monthly, run the command:
$ sudo chgrp root /etc/cron.monthly
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
OVAL test results details

Testing group ownership of /etc/cron.monthly/  oval:ssg-test_file_groupowner_cron_monthly_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_cron_monthly_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.monthlyno valueoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_cron_monthly_0_0:ste:1
Verify Group Who Owns cron.weeklyxccdf_org.ssgproject.content_rule_file_groupowner_cron_weekly mediumCCE-89080-6

Verify Group Who Owns cron.weekly

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_cron_weekly
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_cron_weekly:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-89080-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.5
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/cron.weekly, run the command:
$ sudo chgrp root /etc/cron.weekly
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
OVAL test results details

Testing group ownership of /etc/cron.weekly/  oval:ssg-test_file_groupowner_cron_weekly_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_cron_weekly_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.weeklyno valueoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_cron_weekly_0_0:ste:1
Verify Group Who Owns Crontabxccdf_org.ssgproject.content_rule_file_groupowner_crontab mediumCCE-89062-4

Verify Group Who Owns Crontab

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_crontab
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_crontab:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-89062-4

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.2
pcidss42.2.6, 2.2
Description
To properly set the group owner of /etc/crontab, run the command:
$ sudo chgrp root /etc/crontab
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
OVAL test results details

Testing group ownership of /etc/crontab  oval:ssg-test_file_groupowner_crontab_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_crontab_0:obj:1 of type file_object
FilepathFilterFilter
/etc/crontaboval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_crontab_0_0:ste:1
Verify Owner on cron.dxccdf_org.ssgproject.content_rule_file_owner_cron_d mediumCCE-88741-4

Verify Owner on cron.d

Rule IDxccdf_org.ssgproject.content_rule_file_owner_cron_d
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_cron_d:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88741-4

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.7
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/cron.d, run the command:
$ sudo chown root /etc/cron.d 
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
OVAL test results details

Testing user ownership of /etc/cron.d/  oval:ssg-test_file_owner_cron_d_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_cron_d_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.dno valueoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_cron_d_0_0:ste:1
Verify Owner on cron.dailyxccdf_org.ssgproject.content_rule_file_owner_cron_daily mediumCCE-87499-0

Verify Owner on cron.daily

Rule IDxccdf_org.ssgproject.content_rule_file_owner_cron_daily
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_cron_daily:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-87499-0

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.4
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/cron.daily, run the command:
$ sudo chown root /etc/cron.daily 
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
OVAL test results details

Testing user ownership of /etc/cron.daily/  oval:ssg-test_file_owner_cron_daily_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_cron_daily_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.dailyno valueoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_cron_daily_0_0:ste:1
Verify Owner on cron.hourlyxccdf_org.ssgproject.content_rule_file_owner_cron_hourly mediumCCE-89705-8

Verify Owner on cron.hourly

Rule IDxccdf_org.ssgproject.content_rule_file_owner_cron_hourly
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_cron_hourly:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-89705-8

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.3
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/cron.hourly, run the command:
$ sudo chown root /etc/cron.hourly 
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
OVAL test results details

Testing user ownership of /etc/cron.hourly/  oval:ssg-test_file_owner_cron_hourly_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_cron_hourly_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.hourlyno valueoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_cron_hourly_0_0:ste:1
Verify Owner on cron.monthlyxccdf_org.ssgproject.content_rule_file_owner_cron_monthly mediumCCE-90753-5

Verify Owner on cron.monthly

Rule IDxccdf_org.ssgproject.content_rule_file_owner_cron_monthly
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_cron_monthly:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-90753-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.6
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/cron.monthly, run the command:
$ sudo chown root /etc/cron.monthly 
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
OVAL test results details

Testing user ownership of /etc/cron.monthly/  oval:ssg-test_file_owner_cron_monthly_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_cron_monthly_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.monthlyno valueoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_cron_monthly_0_0:ste:1
Verify Owner on cron.weeklyxccdf_org.ssgproject.content_rule_file_owner_cron_weekly mediumCCE-88943-6

Verify Owner on cron.weekly

Rule IDxccdf_org.ssgproject.content_rule_file_owner_cron_weekly
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_cron_weekly:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88943-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.5
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/cron.weekly, run the command:
$ sudo chown root /etc/cron.weekly 
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
OVAL test results details

Testing user ownership of /etc/cron.weekly/  oval:ssg-test_file_owner_cron_weekly_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_cron_weekly_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/cron.weeklyno valueoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_cron_weekly_0_0:ste:1
Verify Owner on crontabxccdf_org.ssgproject.content_rule_file_owner_crontab mediumCCE-87294-5

Verify Owner on crontab

Rule IDxccdf_org.ssgproject.content_rule_file_owner_crontab
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_crontab:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-87294-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.2
pcidss42.2.6, 2.2
Description
To properly set the owner of /etc/crontab, run the command:
$ sudo chown root /etc/crontab 
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
OVAL test results details

Testing user ownership of /etc/crontab  oval:ssg-test_file_owner_crontab_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_crontab_0:obj:1 of type file_object
FilepathFilterFilter
/etc/crontaboval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_crontab_0_0:ste:1
Verify Permissions on cron.dxccdf_org.ssgproject.content_rule_file_permissions_cron_d mediumCCE-86651-7

Verify Permissions on cron.d

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_cron_d
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_cron_d:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-86651-7

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.7
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/cron.d, run the command:
$ sudo chmod 0700 /etc/cron.d
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

find -H /etc/cron.d/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86651-7
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_d
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Find /etc/cron.d/ file(s)
  command: 'find -L /etc/cron.d/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt  -type d '
  register: files_found
  changed_when: false
  failed_when: false
  check_mode: false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86651-7
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_d
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set permissions for /etc/cron.d/ file(s)
  file:
    path: '{{ item }}'
    mode: u-s,g-xwrs,o-xwrt
    state: directory
  with_items:
  - '{{ files_found.stdout_lines }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86651-7
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_d
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Testing mode of /etc/cron.d/  oval:ssg-test_file_permissions_cron_d_0:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/etc/cron.d/directory0021rwxr-xr-x 
Verify Permissions on cron.dailyxccdf_org.ssgproject.content_rule_file_permissions_cron_daily mediumCCE-88919-6

Verify Permissions on cron.daily

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_cron_daily
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_cron_daily:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88919-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.4
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/cron.daily, run the command:
$ sudo chmod 0700 /etc/cron.daily
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

find -H /etc/cron.daily/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88919-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_daily
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Find /etc/cron.daily/ file(s)
  command: 'find -L /etc/cron.daily/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt  -type d '
  register: files_found
  changed_when: false
  failed_when: false
  check_mode: false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88919-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_daily
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set permissions for /etc/cron.daily/ file(s)
  file:
    path: '{{ item }}'
    mode: u-s,g-xwrs,o-xwrt
    state: directory
  with_items:
  - '{{ files_found.stdout_lines }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88919-6
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_daily
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Testing mode of /etc/cron.daily/  oval:ssg-test_file_permissions_cron_daily_0:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/etc/cron.daily/directory0043rwxr-xr-x 
Verify Permissions on cron.hourlyxccdf_org.ssgproject.content_rule_file_permissions_cron_hourly mediumCCE-88664-8

Verify Permissions on cron.hourly

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_cron_hourly
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_cron_hourly:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88664-8

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.3
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/cron.hourly, run the command:
$ sudo chmod 0700 /etc/cron.hourly
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

find -H /etc/cron.hourly/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88664-8
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_hourly
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Find /etc/cron.hourly/ file(s)
  command: 'find -L /etc/cron.hourly/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt  -type
    d '
  register: files_found
  changed_when: false
  failed_when: false
  check_mode: false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88664-8
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_hourly
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set permissions for /etc/cron.hourly/ file(s)
  file:
    path: '{{ item }}'
    mode: u-s,g-xwrs,o-xwrt
    state: directory
  with_items:
  - '{{ files_found.stdout_lines }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88664-8
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_hourly
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Testing mode of /etc/cron.hourly/  oval:ssg-test_file_permissions_cron_hourly_0:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/etc/cron.hourly/directory0022rwxr-xr-x 
Verify Permissions on cron.monthlyxccdf_org.ssgproject.content_rule_file_permissions_cron_monthly mediumCCE-86632-7

Verify Permissions on cron.monthly

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_cron_monthly
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_cron_monthly:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-86632-7

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.6
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/cron.monthly, run the command:
$ sudo chmod 0700 /etc/cron.monthly
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

find -H /etc/cron.monthly/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86632-7
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_monthly
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Find /etc/cron.monthly/ file(s)
  command: 'find -L /etc/cron.monthly/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt  -type
    d '
  register: files_found
  changed_when: false
  failed_when: false
  check_mode: false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86632-7
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_monthly
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set permissions for /etc/cron.monthly/ file(s)
  file:
    path: '{{ item }}'
    mode: u-s,g-xwrs,o-xwrt
    state: directory
  with_items:
  - '{{ files_found.stdout_lines }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86632-7
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_monthly
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Testing mode of /etc/cron.monthly/  oval:ssg-test_file_permissions_cron_monthly_0:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/etc/cron.monthly/directory006rwxr-xr-x 
Verify Permissions on cron.weeklyxccdf_org.ssgproject.content_rule_file_permissions_cron_weekly mediumCCE-89733-0

Verify Permissions on cron.weekly

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_cron_weekly
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_cron_weekly:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-89733-0

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.5
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/cron.weekly, run the command:
$ sudo chmod 0700 /etc/cron.weekly
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

find -H /etc/cron.weekly/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89733-0
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_weekly
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Find /etc/cron.weekly/ file(s)
  command: 'find -L /etc/cron.weekly/ -maxdepth 1 -perm /u+s,g+xwrs,o+xwrt  -type
    d '
  register: files_found
  changed_when: false
  failed_when: false
  check_mode: false
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89733-0
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_weekly
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set permissions for /etc/cron.weekly/ file(s)
  file:
    path: '{{ item }}'
    mode: u-s,g-xwrs,o-xwrt
    state: directory
  with_items:
  - '{{ files_found.stdout_lines }}'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89733-0
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_cron_weekly
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Testing mode of /etc/cron.weekly/  oval:ssg-test_file_permissions_cron_weekly_0:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/etc/cron.weekly/directory006rwxr-xr-x 
Verify Permissions on crontabxccdf_org.ssgproject.content_rule_file_permissions_crontab mediumCCE-90078-7

Verify Permissions on crontab

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_crontab
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_crontab:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-90078-7

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
cis2.4.1.2
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/crontab, run the command:
$ sudo chmod 0600 /etc/crontab
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

chmod u-xs,g-xwrs,o-xwrt /etc/crontab

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90078-7
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_crontab
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Test for existence /etc/crontab
  stat:
    path: /etc/crontab
  register: file_exists
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90078-7
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_crontab
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xwrs,o-xwrt on /etc/crontab
  file:
    path: /etc/crontab
    mode: u-xs,g-xwrs,o-xwrt
  when:
  - '"kernel" in ansible_facts.packages'
  - file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-90078-7
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_crontab
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

Testing mode of /etc/crontab  oval:ssg-test_file_permissions_crontab_0:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathTypeUIDGIDSize (B)Permissions
not evaluated/etc/crontabregular00451rw-r--r-- 
Uninstall kea Packagexccdf_org.ssgproject.content_rule_package_kea_removed mediumCCE-86596-4

Uninstall kea Package

Rule IDxccdf_org.ssgproject.content_rule_package_kea_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_kea_removed:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-86596-4

References:
anssiR62
cis2.1.3
Description
If the system does not need to act as a DHCP server, the kea package can be uninstalled.
Rationale
Removing the DHCP server ensures that it cannot be easily or accidentally reactivated and disrupt network operation.
OVAL test results details

package kea is removed  oval:ssg-test_package_kea_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_kea_removed:obj:1 of type rpminfo_object
Name
kea
Uninstall bind Packagexccdf_org.ssgproject.content_rule_package_bind_removed lowCCE-87806-6

Uninstall bind Package

Rule IDxccdf_org.ssgproject.content_rule_package_bind_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_bind_removed:def:1
Time2025-07-22T08:31:58+00:00
Severitylow
Identifiers:

CCE-87806-6

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
cis2.1.4
Description
The named service is provided by the bind package. The bind package can be removed with the following command:
$ sudo dnf remove bind
Rationale
If there is no need to make DNS server software available, removing it provides a safeguard against its activation.
OVAL test results details

package bind is removed  oval:ssg-test_package_bind_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_bind_removed:obj:1 of type rpminfo_object
Name
bind
Uninstall dnsmasq Packagexccdf_org.ssgproject.content_rule_package_dnsmasq_removed lowCCE-86558-4

Uninstall dnsmasq Package

Rule IDxccdf_org.ssgproject.content_rule_package_dnsmasq_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_dnsmasq_removed:def:1
Time2025-07-22T08:31:58+00:00
Severitylow
Identifiers:

CCE-86558-4

References:
cis2.1.5
Description
dnsmasq is a lightweight tool that provides DNS caching, DNS forwarding and DHCP (Dynamic Host Configuration Protocol) services.
The dnsmasq package can be removed with the following command:
$ sudo dnf remove dnsmasq
Rationale
Unless a system is specifically designated to act as a DNS caching, DNS forwarding and/or DHCP server, it is recommended that the package be removed to reduce the potential attack surface.
OVAL test results details

package dnsmasq is removed  oval:ssg-test_package_dnsmasq_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_dnsmasq_removed:obj:1 of type rpminfo_object
Name
dnsmasq
Uninstall vsftpd Packagexccdf_org.ssgproject.content_rule_package_vsftpd_removed highCCE-88674-7

Uninstall vsftpd Package

Rule IDxccdf_org.ssgproject.content_rule_package_vsftpd_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_vsftpd_removed:def:1
Time2025-07-22T08:31:58+00:00
Severityhigh
Identifiers:

CCE-88674-7

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a), IA-5(1)(c), IA-5(1).1(v), CM-7, CM-7.1(ii)
nist-csfPR.IP-1, PR.PT-3
os-srgSRG-OS-000074-GPOS-00042, SRG-OS-000095-GPOS-00049, SRG-OS-000480-GPOS-00227
cis2.1.7
Description
The vsftpd package can be removed with the following command:
 $ sudo dnf remove vsftpd
Rationale
Removing the vsftpd package decreases the risk of its accidental activation.
OVAL test results details

package vsftpd is removed  oval:ssg-test_package_vsftpd_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_vsftpd_removed:obj:1 of type rpminfo_object
Name
vsftpd
Remove ftp Packagexccdf_org.ssgproject.content_rule_package_ftp_removed lowCCE-86687-1

Remove ftp Package

Rule IDxccdf_org.ssgproject.content_rule_package_ftp_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_ftp_removed:def:1
Time2025-07-22T08:31:58+00:00
Severitylow
Identifiers:

CCE-86687-1

References:
cis2.2.1
pcidss42.2.4, 2.2
Description
FTP (File Transfer Protocol) is a traditional and widely used standard tool for transferring files between a server and clients over a network, especially where no authentication is necessary (permits anonymous users to connect to a server).
The ftp package can be removed with the following command:
$ sudo dnf remove ftp
Rationale
FTP does not protect the confidentiality of data or authentication credentials. It is recommended SFTP be used if file transfer is required. Unless there is a need to run the system as a FTP server (for example, to allow anonymous downloads), it is recommended that the package be removed to reduce the potential attack surface.
OVAL test results details

package ftp is removed  oval:ssg-test_package_ftp_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_ftp_removed:obj:1 of type rpminfo_object
Name
ftp
Uninstall httpd Packagexccdf_org.ssgproject.content_rule_package_httpd_removed unknownCCE-89436-0

Uninstall httpd Package

Rule IDxccdf_org.ssgproject.content_rule_package_httpd_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_httpd_removed:def:1
Time2025-07-22T08:31:58+00:00
Severityunknown
Identifiers:

CCE-89436-0

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
cis2.1.18
Description
The httpd package can be removed with the following command:
$ sudo dnf remove httpd
Rationale
If there is no need to make the web server software available, removing it provides a safeguard against its activation.
OVAL test results details

package httpd is removed  oval:ssg-test_package_httpd_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_httpd_removed:obj:1 of type rpminfo_object
Name
httpd
Uninstall nginx Packagexccdf_org.ssgproject.content_rule_package_nginx_removed unknownCCE-89648-0

Uninstall nginx Package

Rule IDxccdf_org.ssgproject.content_rule_package_nginx_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_nginx_removed:def:1
Time2025-07-22T08:31:58+00:00
Severityunknown
Identifiers:

CCE-89648-0

References:
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
cis2.1.18
Description
The nginx package can be removed with the following command:
$ sudo dnf remove nginx
Rationale
If there is no need to make the web server software available, removing it provides a safeguard against its activation.
OVAL test results details

package nginx is removed  oval:ssg-test_package_nginx_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_nginx_removed:obj:1 of type rpminfo_object
Name
nginx
Uninstall cyrus-imapd Packagexccdf_org.ssgproject.content_rule_package_cyrus-imapd_removed unknownCCE-90156-1

Uninstall cyrus-imapd Package

Rule IDxccdf_org.ssgproject.content_rule_package_cyrus-imapd_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_cyrus-imapd_removed:def:1
Time2025-07-22T08:31:58+00:00
Severityunknown
Identifiers:

CCE-90156-1

References:
cis2.1.8
Description
The cyrus-imapd package can be removed with the following command:
$ sudo dnf remove cyrus-imapd
Rationale
If there is no need to make the cyrus-imapd software available, removing it provides a safeguard against its activation.
OVAL test results details

package cyrus-imapd is removed  oval:ssg-test_package_cyrus-imapd_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_cyrus-imapd_removed:obj:1 of type rpminfo_object
Name
cyrus-imapd
Uninstall dovecot Packagexccdf_org.ssgproject.content_rule_package_dovecot_removed unknownCCE-86197-1

Uninstall dovecot Package

Rule IDxccdf_org.ssgproject.content_rule_package_dovecot_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_dovecot_removed:def:1
Time2025-07-22T08:31:58+00:00
Severityunknown
Identifiers:

CCE-86197-1

References:
cis2.1.8
Description
The dovecot package can be removed with the following command:
$ sudo dnf remove dovecot
Rationale
If there is no need to make the Dovecot software available, removing it provides a safeguard against its activation.
OVAL test results details

package dovecot is removed  oval:ssg-test_package_dovecot_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_dovecot_removed:obj:1 of type rpminfo_object
Name
dovecot
Ensure LDAP client is not installedxccdf_org.ssgproject.content_rule_package_openldap-clients_removed lowCCE-90641-2

Ensure LDAP client is not installed

Rule IDxccdf_org.ssgproject.content_rule_package_openldap-clients_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_openldap-clients_removed:def:1
Time2025-07-22T08:31:58+00:00
Severitylow
Identifiers:

CCE-90641-2

References:
cis2.2.2
Description
The Lightweight Directory Access Protocol (LDAP) is a service that provides a method for looking up information from a central database. The openldap-clients package can be removed with the following command:
$ sudo dnf remove openldap-clients
Rationale
If the system does not need to act as an LDAP client, it is recommended that the software is removed to reduce the potential attack surface.
OVAL test results details

package openldap-clients is removed  oval:ssg-test_package_openldap-clients_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_openldap-clients_removed:obj:1 of type rpminfo_object
Name
openldap-clients
Disable Postfix Network Listeningxccdf_org.ssgproject.content_rule_postfix_network_listening_disabled mediumCCE-87280-4

Disable Postfix Network Listening

Rule IDxccdf_org.ssgproject.content_rule_postfix_network_listening_disabled
Result
notapplicable
Multi-check ruleno
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-87280-4

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
anssiR74
cis2.1.21
pcidss41.4.2, 1.4
Description
Edit the file /etc/postfix/main.cf to ensure that only the following inet_interfaces line appears:
inet_interfaces = loopback-only
        
Rationale
This ensures postfix accepts mail messages (such as cron job reports) from the local system only, and not from the network, which protects it from network attack.
Ensure Mail Transfer Agent is not Listening on any non-loopback Addressxccdf_org.ssgproject.content_rule_has_nonlocal_mta mediumCCE-88412-2

Ensure Mail Transfer Agent is not Listening on any non-loopback Address

Rule IDxccdf_org.ssgproject.content_rule_has_nonlocal_mta
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-has_nonlocal_mta:def:1
Time2025-07-22T08:31:58+00:00
Severitymedium
Identifiers:

CCE-88412-2

References:
cis2.1.21
Description
Mail Transfer Agents (MTA), such as sendmail and Postfix, are used to listen for incoming mail and transfer the messages to the appropriate user or mail server. If the system is not intended to be a mail server, it is recommended that the MTA be configured to only process local mail.
Rationale
The software for all Mail Transfer Agents is complex and most have a long history of security issues. While it is important to ensure that the system can process local mail messages, it is not necessary to have the MTA's daemon listening on a port unless the server is intended to be a mail server that receives and processes mail from other systems.
OVAL test results details

mta is not listening on any non-loopback address 25  oval:ssg-tst_nothing_listening_external_mta_port_25:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_listening_port_25:obj:1 of type inetlisteningservers_object
ProtocolLocal addressLocal portFilterFilter
tcp127.0.0.125oval:ssg-ste_not_port_25:ste:1oval:ssg-ste_not_on_localhost:ste:1
Disable rpcbind Servicexccdf_org.ssgproject.content_rule_service_rpcbind_disabled lowCCE-88459-3

Disable rpcbind Service

Rule IDxccdf_org.ssgproject.content_rule_service_rpcbind_disabled
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-service_rpcbind_disabled:def:1
Time2025-07-22T08:31:59+00:00
Severitylow
Identifiers:

CCE-88459-3

References:
cis2.1.12
pcidss42.2.4, 2.2
Description
The rpcbind utility maps RPC services to the ports on which they listen. RPC processes notify rpcbind when they start, registering the ports they are listening on and the RPC program numbers they expect to serve. The rpcbind service redirects the client to the proper port number so it can communicate with the requested service. If the system does not require RPC (such as for NFS servers) then this service should be disabled. The rpcbind service can be disabled with the following command:
$ sudo systemctl mask --now rpcbind.service
Rationale
If the system does not require rpc based services, it is recommended that rpcbind be disabled to reduce the attack surface.
OVAL test results details

package rpcbind is removed  oval:ssg-service_rpcbind_disabled_test_service_rpcbind_package_rpcbind_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_rpcbind_disabled_test_service_rpcbind_package_rpcbind_removed:obj:1 of type rpminfo_object
Name
rpcbind

Test that the rpcbind service is not running  oval:ssg-test_service_not_running_service_rpcbind_disabled_rpcbind:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_not_running_service_rpcbind_disabled_rpcbind:obj:1 of type systemdunitproperty_object
UnitProperty
^rpcbind\.(service|socket)$ActiveState

Test that the property LoadState from the service rpcbind is masked  oval:ssg-test_service_loadstate_is_masked_service_rpcbind_disabled_rpcbind:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_loadstate_is_masked_service_rpcbind_disabled_rpcbind:obj:1 of type systemdunitproperty_object
UnitProperty
^rpcbind\.(service|socket)$LoadState
Disable Network File System (nfs)xccdf_org.ssgproject.content_rule_service_nfs_disabled unknownCCE-88764-6

Disable Network File System (nfs)

Rule IDxccdf_org.ssgproject.content_rule_service_nfs_disabled
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-service_nfs_disabled:def:1
Time2025-07-22T08:31:59+00:00
Severityunknown
Identifiers:

CCE-88764-6

References:
cis-csc11, 12, 14, 15, 16, 18, 3, 5
cobit5DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7
iso27001-2013A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-4, PR.AC-6, PR.PT-3
cis2.1.9
Description
The Network File System (NFS) service allows remote hosts to mount and interact with shared filesystems on the local system. If the local system is not designated as a NFS server then this service should be disabled. The nfs-server service can be disabled with the following command:
$ sudo systemctl mask --now nfs-server.service
Rationale
Unnecessary services should be disabled to decrease the attack surface of the system.
OVAL test results details

package nfs-utils is removed  oval:ssg-service_nfs_disabled_test_service_nfs-server_package_nfs-utils_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_nfs_disabled_test_service_nfs-server_package_nfs-utils_removed:obj:1 of type rpminfo_object
Name
nfs-utils

Test that the nfs-server service is not running  oval:ssg-test_service_not_running_service_nfs_disabled_nfs-server:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_not_running_service_nfs_disabled_nfs-server:obj:1 of type systemdunitproperty_object
UnitProperty
^nfs-server\.(service|socket)$ActiveState

Test that the property LoadState from the service nfs-server is masked  oval:ssg-test_service_loadstate_is_masked_service_nfs_disabled_nfs-server:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_loadstate_is_masked_service_nfs_disabled_nfs-server:obj:1 of type systemdunitproperty_object
UnitProperty
^nfs-server\.(service|socket)$LoadState
A remote time server for Chrony is configuredxccdf_org.ssgproject.content_rule_chronyd_specify_remote_server mediumCCE-86811-7

A remote time server for Chrony is configured

Rule IDxccdf_org.ssgproject.content_rule_chronyd_specify_remote_server
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-chronyd_specify_remote_server:def:1
Time2025-07-22T08:31:59+00:00
Severitymedium
Identifiers:

CCE-86811-7

References:
ism0988, 1405
nistCM-6(a), AU-8(1)(a)
pcidssReq-10.4.3
os-srgSRG-OS-000355-GPOS-00143
anssiR71
cis2.3.2
pcidss410.6.2, 10.6
Description
Chrony is a daemon which implements the Network Time Protocol (NTP). It is designed to synchronize system clocks across a variety of systems and use a source that is highly accurate. More information on chrony can be found at https://chrony-project.org/. Chrony can be configured to be a client and/or a server. Add or edit server or pool lines to /etc/chrony.conf as appropriate:
server <remote-server>
Multiple servers may be configured.
Rationale
If chrony is in use on the system proper configuration is vital to ensuring time synchronization is working properly.
OVAL test results details

Ensure at least one NTP server is set  oval:ssg-test_chronyd_remote_server:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/chrony.confserver 169.254.169.123 prefer iburst minpoll 4 maxpoll 4
Ensure that chronyd is running under chrony user accountxccdf_org.ssgproject.content_rule_chronyd_run_as_chrony_user mediumCCE-89296-8

Ensure that chronyd is running under chrony user account

Rule IDxccdf_org.ssgproject.content_rule_chronyd_run_as_chrony_user
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-chronyd_run_as_chrony_user:def:1
Time2025-07-22T08:31:59+00:00
Severitymedium
Identifiers:

CCE-89296-8

References:
cis2.3.3
pcidss410.6.3, 10.6
Description
chrony is a daemon which implements the Network Time Protocol (NTP). It is designed to synchronize system clocks across a variety of systems and use a source that is highly accurate. More information on chrony can be found at https://chrony-project.org/. Chrony can be configured to be a client and/or a server. To ensure that chronyd is running under chrony user account, remove any -u ... option from OPTIONS other than -u chrony, as chrony is run under its own user by default. This recommendation only applies if chrony is in use on the system.
Rationale
If chrony is in use on the system proper configuration is vital to ensuring time synchronization is working properly.
OVAL test results details

The default chrony user hasn't been overriden  oval:ssg-test_no_user_override:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_user_override:obj:1 of type textfilecontent54_object
BehaviorsFilepathPatternInstance
no value/etc/sysconfig/chronyd^\s*OPTIONS=.*[\s'"]-u(?!\s*chrony\b).*0
Uninstall telnet-server Packagexccdf_org.ssgproject.content_rule_package_telnet-server_removed highCCE-88105-2

Uninstall telnet-server Package

Rule IDxccdf_org.ssgproject.content_rule_package_telnet-server_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_telnet-server_removed:def:1
Time2025-07-22T08:31:59+00:00
Severityhigh
Identifiers:

CCE-88105-2

References:
cis-csc11, 12, 14, 15, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
pcidssReq-2.2.2
os-srgSRG-OS-000095-GPOS-00049
anssiR62
cis2.1.15
pcidss42.2.4, 2.2
Description
The telnet-server package can be removed with the following command:
$ sudo dnf remove telnet-server
Rationale
It is detrimental for operating systems to provide, or install by default, functionality exceeding requirements or mission objectives. These unnecessary capabilities are often overlooked and therefore may remain unsecure. They increase the risk to the platform by providing additional attack vectors.
The telnet service provides an unencrypted remote access service which does not provide for the confidentiality and integrity of user passwords or the remote session. If a privileged user were to login using this service, the privileged user password could be compromised.
Removing the telnet-server package decreases the risk of the telnet service's accidental (or intentional) activation.
OVAL test results details

package telnet-server is removed  oval:ssg-test_package_telnet-server_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_telnet-server_removed:obj:1 of type rpminfo_object
Name
telnet-server
Remove telnet Clientsxccdf_org.ssgproject.content_rule_package_telnet_removed lowCCE-90545-5

Remove telnet Clients

Rule IDxccdf_org.ssgproject.content_rule_package_telnet_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_telnet_removed:def:1
Time2025-07-22T08:31:59+00:00
Severitylow
Identifiers:

CCE-90545-5

References:
cui3.1.13
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
iso27001-2013A.8.2.3, A.13.1.1, A.13.2.1, A.13.2.3, A.14.1.2, A.14.1.3
anssiR62
cis2.2.4
pcidss42.2.4, 2.2
Description
The telnet client allows users to start connections to other systems via the telnet protocol.
Rationale
The telnet protocol is insecure and unencrypted. The use of an unencrypted transmission medium could allow an unauthorized user to steal credentials. The ssh package provides an encrypted session and stronger security and is included in Red Hat Enterprise Linux 10.
OVAL test results details

package telnet is removed  oval:ssg-test_package_telnet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_telnet_removed:obj:1 of type rpminfo_object
Name
telnet
Uninstall tftp-server Packagexccdf_org.ssgproject.content_rule_package_tftp-server_removed highCCE-89287-7

Uninstall tftp-server Package

Rule IDxccdf_org.ssgproject.content_rule_package_tftp-server_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_tftp-server_removed:def:1
Time2025-07-22T08:31:59+00:00
Severityhigh
Identifiers:

CCE-89287-7

References:
cis-csc11, 12, 14, 15, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR62
cis2.1.16
pcidss42.2.4, 2.2
Description
The tftp-server package can be removed with the following command:
 $ sudo dnf remove tftp-server
Rationale
Removing the tftp-server package decreases the risk of the accidental (or intentional) activation of tftp services.

If TFTP is required for operational support (such as transmission of router configurations), its use must be documented with the Information Systems Securty Manager (ISSM), restricted to only authorized personnel, and have access control rules established.
OVAL test results details

package tftp-server is removed  oval:ssg-test_package_tftp-server_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_tftp-server_removed:obj:1 of type rpminfo_object
Name
tftp-server
Remove tftp Daemonxccdf_org.ssgproject.content_rule_package_tftp_removed lowCCE-88586-3

Remove tftp Daemon

Rule IDxccdf_org.ssgproject.content_rule_package_tftp_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_tftp_removed:def:1
Time2025-07-22T08:31:59+00:00
Severitylow
Identifiers:

CCE-88586-3

References:
os-srgSRG-OS-000074-GPOS-00042
anssiR62
cis2.2.5
pcidss42.2.4, 2.2
Description
Trivial File Transfer Protocol (TFTP) is a simple file transfer protocol, typically used to automatically transfer configuration or boot files between systems. TFTP does not support authentication and can be easily hacked. The package tftp is a client program that allows for connections to a tftp server.
Rationale
It is recommended that TFTP be removed, unless there is a specific need for TFTP (such as a boot server). In that case, use extreme caution when configuring the services.
OVAL test results details

package tftp is removed  oval:ssg-test_package_tftp_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_tftp_removed:obj:1 of type rpminfo_object
Name
tftp
Uninstall rsync Packagexccdf_org.ssgproject.content_rule_package_rsync_removed mediumCCE-88025-2

Uninstall rsync Package

Rule IDxccdf_org.ssgproject.content_rule_package_rsync_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_rsync_removed:def:1
Time2025-07-22T08:31:59+00:00
Severitymedium
Identifiers:

CCE-88025-2

References:
cis2.1.13
Description
The rsyncd service can be used to synchronize files between systems over network links. The rsync-daemon package can be removed with the following command:
$ sudo dnf remove rsync-daemon
Rationale
The rsyncd service presents a security risk as it uses unencrypted protocols for communication.
OVAL test results details

package rsync-daemon is removed  oval:ssg-test_package_rsync-daemon_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_rsync-daemon_removed:obj:1 of type rpminfo_object
Name
rsync-daemon
Disable the CUPS Servicexccdf_org.ssgproject.content_rule_service_cups_disabled unknownCCE-86174-0

Disable the CUPS Service

Rule IDxccdf_org.ssgproject.content_rule_service_cups_disabled
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-service_cups_disabled:def:1
Time2025-07-22T08:32:00+00:00
Severityunknown
Identifiers:

CCE-86174-0

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
cis2.1.11
Description
The cups service can be disabled with the following command:
$ sudo systemctl mask --now cups.service
Rationale
Turn off unneeded services to reduce attack surface.
OVAL test results details

package cups is removed  oval:ssg-service_cups_disabled_test_service_cups_package_cups_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_cups_disabled_test_service_cups_package_cups_removed:obj:1 of type rpminfo_object
Name
cups

Test that the cups service is not running  oval:ssg-test_service_not_running_service_cups_disabled_cups:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_not_running_service_cups_disabled_cups:obj:1 of type systemdunitproperty_object
UnitProperty
^cups\.(service|socket)$ActiveState

Test that the property LoadState from the service cups is masked  oval:ssg-test_service_loadstate_is_masked_service_cups_disabled_cups:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_service_loadstate_is_masked_service_cups_disabled_cups:obj:1 of type systemdunitproperty_object
UnitProperty
^cups\.(service|socket)$LoadState
Uninstall squid Packagexccdf_org.ssgproject.content_rule_package_squid_removed unknownCCE-87396-8

Uninstall squid Package

Rule IDxccdf_org.ssgproject.content_rule_package_squid_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_squid_removed:def:1
Time2025-07-22T08:32:00+00:00
Severityunknown
Identifiers:

CCE-87396-8

References:
cis2.1.17
Description
The squid package can be removed with the following command:
 $ sudo dnf remove squid
Rationale
If there is no need to make the proxy server software available, removing it provides a safeguard against its activation.
OVAL test results details

package squid is removed  oval:ssg-test_package_squid_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_squid_removed:obj:1 of type rpminfo_object
Name
squid
Uninstall Samba Packagexccdf_org.ssgproject.content_rule_package_samba_removed unknownCCE-89779-3

Uninstall Samba Package

Rule IDxccdf_org.ssgproject.content_rule_package_samba_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_samba_removed:def:1
Time2025-07-22T08:32:00+00:00
Severityunknown
Identifiers:

CCE-89779-3

References:
cis2.1.6
Description
The samba package can be removed with the following command:
 $ sudo dnf remove samba
Rationale
If there is no need to make the Samba software available, removing it provides a safeguard against its activation.
OVAL test results details

package samba is removed  oval:ssg-test_package_samba_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_samba_removed:obj:1 of type rpminfo_object
Name
samba
Uninstall net-snmp Packagexccdf_org.ssgproject.content_rule_package_net-snmp_removed unknownCCE-89193-7

Uninstall net-snmp Package

Rule IDxccdf_org.ssgproject.content_rule_package_net-snmp_removed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_net-snmp_removed:def:1
Time2025-07-22T08:32:00+00:00
Severityunknown
Identifiers:

CCE-89193-7

References:
cis2.1.14
pcidss42.2.4, 2.2
Description
The net-snmp package provides the snmpd service. The net-snmp package can be removed with the following command:
$ sudo dnf remove net-snmp
Rationale
If there is no need to run SNMP server software, removing the package provides a safeguard against its activation.
OVAL test results details

package net-snmp is removed  oval:ssg-test_package_net-snmp_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_test_package_net-snmp_removed:obj:1 of type rpminfo_object
Name
net-snmp
Set SSH Client Alive Count Maxxccdf_org.ssgproject.content_rule_sshd_set_keepalive mediumCCE-86794-5

Set SSH Client Alive Count Max

Rule IDxccdf_org.ssgproject.content_rule_sshd_set_keepalive
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_set_keepalive:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-86794-5

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8
cjis5.5.6
cobit5APO13.01, BAI03.01, BAI03.02, BAI03.03, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.1.11
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2
iso27001-2013A.12.4.1, A.12.4.3, A.14.1.1, A.14.2.1, A.14.2.5, A.18.1.4, A.6.1.2, A.6.1.5, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistAC-2(5), AC-12, AC-17(a), SC-10, CM-6(a)
nist-csfDE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.IP-2
pcidssReq-8.1.8
os-srgSRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109
cis5.1.9
pcidss48.2.8, 8.2
Description
The SSH server sends at most ClientAliveCountMax messages during a SSH session and waits for a response from the SSH client. The option ClientAliveInterval configures timeout after each ClientAliveCountMax message. If the SSH server does not receive a response from the client, then the connection is considered unresponsive and terminated. For SSH earlier than v8.2, a ClientAliveCountMax value of 0 causes a timeout precisely when the ClientAliveInterval is set. Starting with v8.2, a value of 0 disables the timeout functionality completely. If the option is set to a number greater than 0, then the session will be disconnected after ClientAliveInterval * ClientAliveCountMax seconds without receiving a keep alive message.
Rationale
This ensures a user login will be terminated as soon as the ClientAliveInterval is reached.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

var_sshd_set_keepalive='1'


mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

LC_ALL=C sed -i "/^\s*ClientAliveCountMax\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*ClientAliveCountMax\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*ClientAliveCountMax\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
    touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "ClientAliveCountMax $var_sshd_set_keepalive" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86794-5
  - CJIS-5.5.6
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.8
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_keepalive
- name: XCCDF Value var_sshd_set_keepalive # promote to variable
  set_fact:
    var_sshd_set_keepalive: !!str 1
  tags:
    - always

- name: Set SSH Client Alive Count Max
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter ClientAliveCountMax is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+
      line: ClientAliveCountMax {{ var_sshd_set_keepalive }}
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86794-5
  - CJIS-5.5.6
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.8
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_keepalive

- name: Set SSH Client Alive Count Max - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86794-5
  - CJIS-5.5.6
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.8
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_keepalive
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of ClientAliveCountMax setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_set_keepalive:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_set_keepalive:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[ \t]*(?i)ClientAliveCountMax(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

tests the value of ClientAliveCountMax setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_set_keepalive_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_set_keepalive_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[ \t]*(?i)ClientAliveCountMax(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

Verify that the value of ClientAliveCountMax is present  oval:ssg-test_ClientAliveCountMax_present_sshd_set_keepalive:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_sshd_set_keepalive:obj:1 of type textfilecontent54_object
Set
oval:ssg-obj_sshd_set_keepalive:obj:1 oval:ssg-obj_sshd_set_keepalive_config_dir:obj:1
Set SSH Client Alive Intervalxccdf_org.ssgproject.content_rule_sshd_set_idle_timeout mediumCCE-90362-5

Set SSH Client Alive Interval

Rule IDxccdf_org.ssgproject.content_rule_sshd_set_idle_timeout
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_set_idle_timeout:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-90362-5

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8
cjis5.5.6
cobit5APO13.01, BAI03.01, BAI03.02, BAI03.03, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.1.11
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2
iso27001-2013A.12.4.1, A.12.4.3, A.14.1.1, A.14.2.1, A.14.2.5, A.18.1.4, A.6.1.2, A.6.1.5, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistCM-6(a), AC-17(a), AC-2(5), AC-12, AC-17(a), SC-10, CM-6(a)
nist-csfDE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.IP-2
pcidssReq-8.1.8
os-srgSRG-OS-000126-GPOS-00066, SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109, SRG-OS-000395-GPOS-00175
cis5.1.9
pcidss48.2.8, 8.2
Description
SSH allows administrators to set a network responsiveness timeout interval. After this interval has passed, the unresponsive client will be automatically logged out.

To set this timeout interval, edit the following line in /etc/ssh/sshd_config as follows:
ClientAliveInterval 300
        


The timeout interval is given in seconds. For example, have a timeout of 10 minutes, set interval to 600.

If a shorter timeout has already been set for the login shell, that value will preempt any SSH setting made in /etc/ssh/sshd_config. Keep in mind that some processes may stop SSH from correctly detecting that the user is idle.
Rationale
Terminating an idle ssh session within a short time period reduces the window of opportunity for unauthorized personnel to take control of a management session enabled on the console or console port that has been let unattended.
Warnings
warning  SSH disconnecting unresponsive clients will not have desired effect without also configuring ClientAliveCountMax in the SSH service configuration.
warning  Following conditions may prevent the SSH session to time out:
  • Remote processes on the remote machine generates output. As the output has to be transferred over the network to the client, the timeout is reset every time such transfer happens.
  • Any scp or sftp activity by the same user to the host resets the timeout.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

sshd_idle_timeout_value='300'


mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

LC_ALL=C sed -i "/^\s*ClientAliveInterval\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*ClientAliveInterval\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*ClientAliveInterval\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
    touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "ClientAliveInterval $sshd_idle_timeout_value" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90362-5
  - CJIS-5.5.6
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.8
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_idle_timeout
- name: XCCDF Value sshd_idle_timeout_value # promote to variable
  set_fact:
    sshd_idle_timeout_value: !!str 300
  tags:
    - always

- name: Set SSH Client Alive Interval
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter ClientAliveInterval is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+
      line: ClientAliveInterval {{ sshd_idle_timeout_value }}
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90362-5
  - CJIS-5.5.6
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.8
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_idle_timeout

- name: Set SSH Client Alive Interval - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90362-5
  - CJIS-5.5.6
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.8
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_idle_timeout
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

timeout is configured  oval:ssg-test_sshd_idle_timeout:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_sshd_idle_timeout:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[\s]*(?i)ClientAliveInterval[\s]+(\d+)[\s]*(?:#.*)?$1

timeout is configured in config directory  oval:ssg-test_sshd_idle_timeout_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_sshd_idle_timeout_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[\s]*(?i)ClientAliveInterval[\s]+(\d+)[\s]*(?:#.*)?$1

Verify that the value of ClientAliveInterval is present  oval:ssg-test_clientaliveinterval_present:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_sshd_set_idle_timeout:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_sshd_idle_timeout:obj:1 oval:ssg-object_sshd_idle_timeout_config_dir:obj:1
Disable Host-Based Authenticationxccdf_org.ssgproject.content_rule_disable_host_auth mediumCCE-88057-5

Disable Host-Based Authentication

Rule IDxccdf_org.ssgproject.content_rule_disable_host_auth
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-disable_host_auth:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-88057-5

References:
cis-csc11, 12, 14, 15, 16, 18, 3, 5, 9
cjis5.5.6
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06
cui3.1.12
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistAC-3, AC-17(a), CM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-4, PR.AC-6, PR.IP-1, PR.PT-3
osppFIA_UAU.1
os-srgSRG-OS-000480-GPOS-00229
cis5.1.12
pcidss48.3.1, 8.3
Description
SSH's cryptographic host-based authentication is more secure than .rhosts authentication. However, it is not recommended that hosts unilaterally trust one another, even within an organization.
The default SSH configuration disables host-based authentication. The appropriate configuration is used if no value is set for HostbasedAuthentication.
To explicitly disable host-based authentication, add or correct the following line in /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf:
HostbasedAuthentication no
Rationale
SSH trust relationships mean a compromise on one host can allow an attacker to move trivially to other hosts.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf

LC_ALL=C sed -i "/^\s*HostbasedAuthentication\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*HostbasedAuthentication\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*HostbasedAuthentication\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
else
    touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"

cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "HostbasedAuthentication no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88057-5
  - CJIS-5.5.6
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.1
  - disable_host_auth
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Disable Host-Based Authentication
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter HostbasedAuthentication is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+
      line: HostbasedAuthentication no
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88057-5
  - CJIS-5.5.6
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.1
  - disable_host_auth
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Disable Host-Based Authentication - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88057-5
  - CJIS-5.5.6
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.1
  - disable_host_auth
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,%23%09%24OpenBSD%3A%20sshd_config%2Cv%201.103%202018%2F04%2F09%2020%3A41%3A22%20tj%20Exp%20%24%0A%0A%23%20This%20is%20the%20sshd%20server%20system-wide%20configuration%20file.%20%20See%0A%23%20sshd_config%285%29%20for%20more%20information.%0A%0A%23%20This%20sshd%20was%20compiled%20with%20PATH%3D%2Fusr%2Flocal%2Fbin%3A%2Fusr%2Fbin%3A%2Fusr%2Flocal%2Fsbin%3A%2Fusr%2Fsbin%0A%0A%23%20The%20strategy%20used%20for%20options%20in%20the%20default%20sshd_config%20shipped%20with%0A%23%20OpenSSH%20is%20to%20specify%20options%20with%20their%20default%20value%20where%0A%23%20possible%2C%20but%20leave%20them%20commented.%20%20Uncommented%20options%20override%20the%0A%23%20default%20value.%0A%0A%23%20If%20you%20want%20to%20change%20the%20port%20on%20a%20SELinux%20system%2C%20you%20have%20to%20tell%0A%23%20SELinux%20about%20this%20change.%0A%23%20semanage%20port%20-a%20-t%20ssh_port_t%20-p%20tcp%20%23PORTNUMBER%0A%23%0A%23Port%2022%0A%23AddressFamily%20any%0A%23ListenAddress%200.0.0.0%0A%23ListenAddress%20%3A%3A%0A%0AHostKey%20%2Fetc%2Fssh%2Fssh_host_rsa_key%0AHostKey%20%2Fetc%2Fssh%2Fssh_host_ecdsa_key%0AHostKey%20%2Fetc%2Fssh%2Fssh_host_ed25519_key%0A%0A%23%20Ciphers%20and%20keying%0ARekeyLimit%20512M%201h%0A%0A%23%20System-wide%20Crypto%20policy%3A%0A%23%20This%20system%20is%20following%20system-wide%20crypto%20policy.%20The%20changes%20to%0A%23%20Ciphers%2C%20MACs%2C%20KexAlgoritms%20and%20GSSAPIKexAlgorithsm%20will%20not%20have%20any%0A%23%20effect%20here.%20They%20will%20be%20overridden%20by%20command-line%20options%20passed%20on%0A%23%20the%20server%20start%20up.%0A%23%20To%20opt%20out%2C%20uncomment%20a%20line%20with%20redefinition%20of%20%20CRYPTO_POLICY%3D%0A%23%20variable%20in%20%20%2Fetc%2Fsysconfig%2Fsshd%20%20to%20overwrite%20the%20policy.%0A%23%20For%20more%20information%2C%20see%20manual%20page%20for%20update-crypto-policies%288%29.%0A%0A%23%20Logging%0A%23SyslogFacility%20AUTH%0ASyslogFacility%20AUTHPRIV%0A%23LogLevel%20INFO%0A%0A%23%20Authentication%3A%0A%0A%23LoginGraceTime%202m%0APermitRootLogin%20no%0AStrictModes%20yes%0A%23MaxAuthTries%206%0A%23MaxSessions%2010%0A%0APubkeyAuthentication%20yes%0A%0A%23%20The%20default%20is%20to%20check%20both%20.ssh%2Fauthorized_keys%20and%20.ssh%2Fauthorized_keys2%0A%23%20but%20this%20is%20overridden%20so%20installations%20will%20only%20check%20.ssh%2Fauthorized_keys%0AAuthorizedKeysFile%09.ssh%2Fauthorized_keys%0A%0A%23AuthorizedPrincipalsFile%20none%0A%0A%23AuthorizedKeysCommand%20none%0A%23AuthorizedKeysCommandUser%20nobody%0A%0A%23%20For%20this%20to%20work%20you%20will%20also%20need%20host%20keys%20in%20%2Fetc%2Fssh%2Fssh_known_hosts%0AHostbasedAuthentication%20no%0A%23%20Change%20to%20yes%20if%20you%20don%27t%20trust%20~%2F.ssh%2Fknown_hosts%20for%0A%23%20HostbasedAuthentication%0AIgnoreUserKnownHosts%20yes%0A%23%20Don%27t%20read%20the%20user%27s%20~%2F.rhosts%20and%20~%2F.shosts%20files%0AIgnoreRhosts%20yes%0A%0A%23%20To%20disable%20tunneled%20clear%20text%20passwords%2C%20change%20to%20no%20here%21%0A%23PasswordAuthentication%20yes%0APermitEmptyPasswords%20no%0APasswordAuthentication%20no%0A%0A%23%20Change%20to%20no%20to%20disable%20s%2Fkey%20passwords%0A%23ChallengeResponseAuthentication%20yes%0AChallengeResponseAuthentication%20no%0A%0A%23%20Kerberos%20options%0AKerberosAuthentication%20no%0A%23KerberosOrLocalPasswd%20yes%0A%23KerberosTicketCleanup%20yes%0A%23KerberosGetAFSToken%20no%0A%23KerberosUseKuserok%20yes%0A%0A%23%20GSSAPI%20options%0AGSSAPIAuthentication%20no%0AGSSAPICleanupCredentials%20no%0A%23GSSAPIStrictAcceptorCheck%20yes%0A%23GSSAPIKeyExchange%20no%0A%23GSSAPIEnablek5users%20no%0A%0A%23%20Set%20this%20to%20%27yes%27%20to%20enable%20PAM%20authentication%2C%20account%20processing%2C%0A%23%20and%20session%20processing.%20If%20this%20is%20enabled%2C%20PAM%20authentication%20will%0A%23%20be%20allowed%20through%20the%20ChallengeResponseAuthentication%20and%0A%23%20PasswordAuthentication.%20%20Depending%20on%20your%20PAM%20configuration%2C%0A%23%20PAM%20authentication%20via%20ChallengeResponseAuthentication%20may%20bypass%0A%23%20the%20setting%20of%20%22PermitRootLogin%20without-password%22.%0A%23%20If%20you%20just%20want%20the%20PAM%20account%20and%20session%20checks%20to%20run%20without%0A%23%20PAM%20authentication%2C%20then%20enable%20this%20but%20set%20PasswordAuthentication%0A%23%20and%20ChallengeResponseAuthentication%20to%20%27no%27.%0A%23%20WARNING%3A%20%27UsePAM%20no%27%20is%20not%20supported%20in%20Fedora%20and%20may%20cause%20several%0A%23%20problems.%0AUsePAM%20yes%0A%0A%23AllowAgentForwarding%20yes%0A%23AllowTcpForwarding%20yes%0A%23GatewayPorts%20no%0AX11Forwarding%20yes%0A%23X11DisplayOffset%2010%0A%23X11UseLocalhost%20yes%0A%23PermitTTY%20yes%0A%0A%23%20It%20is%20recommended%20to%20use%20pam_motd%20in%20%2Fetc%2Fpam.d%2Fsshd%20instead%20of%20PrintMotd%2C%0A%23%20as%20it%20is%20more%20configurable%20and%20versatile%20than%20the%20built-in%20version.%0APrintMotd%20no%0A%0APrintLastLog%20yes%0A%23TCPKeepAlive%20yes%0APermitUserEnvironment%20no%0ACompression%20no%0AClientAliveInterval%20600%0AClientAliveCountMax%200%0A%23UseDNS%20no%0A%23PidFile%20%2Fvar%2Frun%2Fsshd.pid%0A%23MaxStartups%2010%3A30%3A100%0A%23PermitTunnel%20no%0A%23ChrootDirectory%20none%0A%23VersionAddendum%20none%0A%0A%23%20no%20default%20banner%20path%0ABanner%20%2Fetc%2Fissue%0A%0A%23%20Accept%20locale-related%20environment%20variables%0AAcceptEnv%20LANG%20LC_CTYPE%20LC_NUMERIC%20LC_TIME%20LC_COLLATE%20LC_MONETARY%20LC_MESSAGES%0AAcceptEnv%20LC_PAPER%20LC_NAME%20LC_ADDRESS%20LC_TELEPHONE%20LC_MEASUREMENT%0AAcceptEnv%20LC_IDENTIFICATION%20LC_ALL%20LANGUAGE%0AAcceptEnv%20XMODIFIERS%0A%0A%23%20override%20default%20of%20no%20subsystems%0ASubsystem%09sftp%09%2Fusr%2Flibexec%2Fopenssh%2Fsftp-server%0A%0A%23%20Example%20of%20overriding%20settings%20on%20a%20per-user%20basis%0A%23Match%20User%20anoncvs%0A%23%09X11Forwarding%20no%0A%23%09AllowTcpForwarding%20no%0A%23%09PermitTTY%20no%0A%23%09ForceCommand%20cvs%20server%0A%0AUsePrivilegeSeparation%20sandbox
        mode: 0600
        path: /etc/ssh/sshd_config
        overwrite: true
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of HostbasedAuthentication setting in the /etc/ssh/sshd_config file  oval:ssg-test_disable_host_auth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_disable_host_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[ \t]*(?i)HostbasedAuthentication(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

tests the value of HostbasedAuthentication setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_disable_host_auth_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_disable_host_auth_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[ \t]*(?i)HostbasedAuthentication(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

Verify that the value of HostbasedAuthentication is present  oval:ssg-test_HostbasedAuthentication_present_disable_host_auth:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_disable_host_auth:obj:1 of type textfilecontent54_object
Set
oval:ssg-obj_disable_host_auth:obj:1 oval:ssg-obj_disable_host_auth_config_dir:obj:1
Disable SSH Access via Empty Passwordsxccdf_org.ssgproject.content_rule_sshd_disable_empty_passwords highCCE-86753-1

Disable SSH Access via Empty Passwords

Rule IDxccdf_org.ssgproject.content_rule_sshd_disable_empty_passwords
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_disable_empty_passwords:def:1
Time2025-07-22T08:32:00+00:00
Severityhigh
Identifiers:

CCE-86753-1

References:
cis-csc11, 12, 13, 14, 15, 16, 18, 3, 5, 9
cjis5.5.6
cobit5APO01.06, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06
cui3.1.1, 3.1.5
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 5.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistAC-17(a), CM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-4, PR.AC-6, PR.DS-5, PR.IP-1, PR.PT-3
osppFIA_UAU.1
pcidssReq-2.2.4
os-srgSRG-OS-000106-GPOS-00053, SRG-OS-000480-GPOS-00229, SRG-OS-000480-GPOS-00227
cis5.1.19
pcidss42.2.6, 2.2
Description
Disallow SSH login with empty passwords. The default SSH configuration disables logins with empty passwords. The appropriate configuration is used if no value is set for PermitEmptyPasswords.
To explicitly disallow SSH login from accounts with empty passwords, add or correct the following line in /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf:
PermitEmptyPasswords no
Any accounts with empty passwords should be disabled immediately, and PAM configuration should prevent users from being able to assign themselves empty passwords.
Rationale
Configuring this setting for the SSH daemon provides additional assurance that remote login via SSH will require a password, even in the event of misconfiguration elsewhere.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf

LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
else
    touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"

cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "PermitEmptyPasswords no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86753-1
  - CJIS-5.5.6
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_empty_passwords

- name: Disable SSH Access via Empty Passwords
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter PermitEmptyPasswords is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
      line: PermitEmptyPasswords no
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86753-1
  - CJIS-5.5.6
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_empty_passwords

- name: Disable SSH Access via Empty Passwords - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86753-1
  - CJIS-5.5.6
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_empty_passwords
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of PermitEmptyPasswords setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_disable_empty_passwords:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_disable_empty_passwords:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[ \t]*(?i)PermitEmptyPasswords(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

tests the value of PermitEmptyPasswords setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_disable_empty_passwords_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_disable_empty_passwords_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[ \t]*(?i)PermitEmptyPasswords(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

Verify that the value of PermitEmptyPasswords is present  oval:ssg-test_PermitEmptyPasswords_present_sshd_disable_empty_passwords:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_sshd_disable_empty_passwords:obj:1 of type textfilecontent54_object
Set
oval:ssg-obj_sshd_disable_empty_passwords:obj:1 oval:ssg-obj_sshd_disable_empty_passwords_config_dir:obj:1
Disable GSSAPI Authenticationxccdf_org.ssgproject.content_rule_sshd_disable_gssapi_auth mediumCCE-89145-7

Disable GSSAPI Authentication

Rule IDxccdf_org.ssgproject.content_rule_sshd_disable_gssapi_auth
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_disable_gssapi_auth:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-89145-7

References:
cis-csc11, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05
cui3.1.12
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 7.6
ism0418, 1055, 1402
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-7(a), CM-7(b), CM-6(a), AC-17(a)
nist-csfPR.IP-1
osppFTP_ITC_EXT.1, FCS_SSH_EXT.1.2
os-srgSRG-OS-000364-GPOS-00151, SRG-OS-000480-GPOS-00227
cis5.1.11
Description
Unless needed, SSH should not permit extraneous or unnecessary authentication mechanisms like GSSAPI.
The default SSH configuration disallows authentications based on GSSAPI. The appropriate configuration is used if no value is set for GSSAPIAuthentication.
To explicitly disable GSSAPI authentication, add or correct the following line in /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf:
GSSAPIAuthentication no
Rationale
GSSAPI authentication is used to provide additional authentication mechanisms to applications. Allowing GSSAPI authentication through SSH exposes the system's GSSAPI to remote hosts, increasing the attack surface of the system.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf

LC_ALL=C sed -i "/^\s*GSSAPIAuthentication\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*GSSAPIAuthentication\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*GSSAPIAuthentication\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
else
    touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"

cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "GSSAPIAuthentication no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89145-7
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_gssapi_auth

- name: Disable GSSAPI Authentication
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter GSSAPIAuthentication is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+
      line: GSSAPIAuthentication no
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89145-7
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_gssapi_auth

- name: Disable GSSAPI Authentication - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89145-7
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_gssapi_auth
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of GSSAPIAuthentication setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_disable_gssapi_auth:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_disable_gssapi_auth:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[ \t]*(?i)GSSAPIAuthentication(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

tests the value of GSSAPIAuthentication setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_disable_gssapi_auth_config_dir:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/ssh/sshd_config.d/50-redhat.confGSSAPIAuthentication yes

Verify that the value of GSSAPIAuthentication is present  oval:ssg-test_GSSAPIAuthentication_present_sshd_disable_gssapi_auth:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/ssh/sshd_config.d/50-redhat.confGSSAPIAuthentication yes
Disable SSH Support for .rhosts Filesxccdf_org.ssgproject.content_rule_sshd_disable_rhosts mediumCCE-87777-9

Disable SSH Support for .rhosts Files

Rule IDxccdf_org.ssgproject.content_rule_sshd_disable_rhosts
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_disable_rhosts:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-87777-9

References:
cis-csc11, 12, 14, 15, 16, 18, 3, 5, 9
cjis5.5.6
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06
cui3.1.12
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistAC-17(a), CM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-4, PR.AC-6, PR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
cis5.1.13
pcidss42.2.6, 2.2
Description
SSH can emulate the behavior of the obsolete rsh command in allowing users to enable insecure access to their accounts via .rhosts files.
The default SSH configuration disables support for .rhosts. The appropriate configuration is used if no value is set for IgnoreRhosts.
To explicitly disable support for .rhosts files, add or correct the following line in /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf:
IgnoreRhosts yes
Rationale
SSH trust relationships mean a compromise on one host can allow an attacker to move trivially to other hosts.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf

LC_ALL=C sed -i "/^\s*IgnoreRhosts\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*IgnoreRhosts\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*IgnoreRhosts\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
else
    touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"

cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "IgnoreRhosts yes" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87777-9
  - CJIS-5.5.6
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_rhosts

- name: Disable SSH Support for .rhosts Files
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "IgnoreRhosts"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter IgnoreRhosts is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "IgnoreRhosts"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "IgnoreRhosts"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "IgnoreRhosts"| regex_escape }}\s+
      line: IgnoreRhosts yes
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87777-9
  - CJIS-5.5.6
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_rhosts

- name: Disable SSH Support for .rhosts Files - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87777-9
  - CJIS-5.5.6
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_rhosts
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of IgnoreRhosts setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_disable_rhosts:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_disable_rhosts:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[ \t]*(?i)IgnoreRhosts(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

tests the value of IgnoreRhosts setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_disable_rhosts_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_disable_rhosts_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[ \t]*(?i)IgnoreRhosts(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

Verify that the value of IgnoreRhosts is present  oval:ssg-test_IgnoreRhosts_present_sshd_disable_rhosts:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_sshd_disable_rhosts:obj:1 of type textfilecontent54_object
Set
oval:ssg-obj_sshd_disable_rhosts:obj:1 oval:ssg-obj_sshd_disable_rhosts_config_dir:obj:1
Do Not Allow SSH Environment Optionsxccdf_org.ssgproject.content_rule_sshd_do_not_permit_user_env mediumCCE-87395-0

Do Not Allow SSH Environment Options

Rule IDxccdf_org.ssgproject.content_rule_sshd_do_not_permit_user_env
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_do_not_permit_user_env:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-87395-0

References:
cis-csc11, 3, 9
cjis5.5.6
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05
cui3.1.12
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nistAC-17(a), CM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1
pcidssReq-2.2.4
os-srgSRG-OS-000480-GPOS-00229
cis5.1.21
pcidss42.2.6, 2.2
Description
Ensure that users are not able to override environment variables of the SSH daemon.
The default SSH configuration disables environment processing. The appropriate configuration is used if no value is set for PermitUserEnvironment.
To explicitly disable Environment options, add or correct the following /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf:
PermitUserEnvironment no
Rationale
SSH environment options potentially allow users to bypass access restriction in some configurations.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf

LC_ALL=C sed -i "/^\s*PermitUserEnvironment\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*PermitUserEnvironment\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*PermitUserEnvironment\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
else
    touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"

cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "PermitUserEnvironment no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87395-0
  - CJIS-5.5.6
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_do_not_permit_user_env

- name: Do Not Allow SSH Environment Options
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter PermitUserEnvironment is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+
      line: PermitUserEnvironment no
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87395-0
  - CJIS-5.5.6
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_do_not_permit_user_env

- name: Do Not Allow SSH Environment Options - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87395-0
  - CJIS-5.5.6
  - NIST-800-171-3.1.12
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_do_not_permit_user_env
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of PermitUserEnvironment setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_do_not_permit_user_env:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_do_not_permit_user_env:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[ \t]*(?i)PermitUserEnvironment(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

tests the value of PermitUserEnvironment setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_do_not_permit_user_env_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_do_not_permit_user_env_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[ \t]*(?i)PermitUserEnvironment(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

Verify that the value of PermitUserEnvironment is present  oval:ssg-test_PermitUserEnvironment_present_sshd_do_not_permit_user_env:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_sshd_do_not_permit_user_env:obj:1 of type textfilecontent54_object
Set
oval:ssg-obj_sshd_do_not_permit_user_env:obj:1 oval:ssg-obj_sshd_do_not_permit_user_env_config_dir:obj:1
Enable PAMxccdf_org.ssgproject.content_rule_sshd_enable_pam mediumCCE-87045-1

Enable PAM

Rule IDxccdf_org.ssgproject.content_rule_sshd_enable_pam
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_enable_pam:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-87045-1

References:
os-srgSRG-OS-000125-GPOS-00065
cis5.1.22
pcidss42.2.6, 2.2
Description
UsePAM Enables the Pluggable Authentication Module interface. If set to “yes” this will enable PAM authentication using ChallengeResponseAuthentication and PasswordAuthentication in addition to PAM account and session module processing for all authentication types. To enable PAM authentication, add or correct the following line in /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf:
UsePAM yes
Rationale
When UsePAM is set to yes, PAM runs through account and session types properly. This is important if you want to restrict access to services based off of IP, time or other factors of the account. Additionally, you can make sure users inherit certain environment variables on login or disallow access to the server.
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of UsePAM setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_enable_pam:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_enable_pam:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[ \t]*(?i)UsePAM(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

tests the value of UsePAM setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_enable_pam_config_dir:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/ssh/sshd_config.d/50-redhat.confUsePAM yes

Verify that the value of UsePAM is present  oval:ssg-test_UsePAM_present_sshd_enable_pam:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/ssh/sshd_config.d/50-redhat.confUsePAM yes
Enable SSH Warning Bannerxccdf_org.ssgproject.content_rule_sshd_enable_warning_banner_net mediumCCE-88799-2

Enable SSH Warning Banner

Rule IDxccdf_org.ssgproject.content_rule_sshd_enable_warning_banner_net
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_enable_warning_banner_net:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-88799-2

References:
cjis5.5.6
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.9
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistAC-8(a), AC-8(c), AC-17(a), CM-6(a)
nist-csfPR.AC-7
os-srgSRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088
cis5.1.8
Description
To enable the warning banner and ensure it is consistent across the system, add or correct the following line in /etc/ssh/sshd_config:
Banner /etc/issue.net
Another section contains information on how to create an appropriate system-wide warning banner.
Rationale
The warning message reinforces policy awareness during the logon process and facilitates possible legal action against attackers. Alternatively, systems whose ownership should not be obvious should ensure usage of a banner that does not provide easy attribution.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
    touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "Banner /etc/issue.net" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88799-2
  - CJIS-5.5.6
  - NIST-800-171-3.1.9
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-8(a)
  - NIST-800-53-AC-8(c)
  - NIST-800-53-CM-6(a)
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_enable_warning_banner_net

- name: Enable SSH Warning Banner
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "Banner"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter Banner is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "Banner"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "Banner"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "Banner"| regex_escape }}\s+
      line: Banner /etc/issue.net
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88799-2
  - CJIS-5.5.6
  - NIST-800-171-3.1.9
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-8(a)
  - NIST-800-53-AC-8(c)
  - NIST-800-53-CM-6(a)
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_enable_warning_banner_net

- name: Enable SSH Warning Banner - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88799-2
  - CJIS-5.5.6
  - NIST-800-171-3.1.9
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-8(a)
  - NIST-800-53-AC-8(c)
  - NIST-800-53-CM-6(a)
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_enable_warning_banner_net
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of Banner setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_enable_warning_banner_net:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_enable_warning_banner_net:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[ \t]*(?i)Banner(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

tests the value of Banner setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_enable_warning_banner_net_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_enable_warning_banner_net_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[ \t]*(?i)Banner(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

Verify that the value of Banner is present  oval:ssg-test_Banner_present_sshd_enable_warning_banner_net:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_sshd_enable_warning_banner_net:obj:1 of type textfilecontent54_object
Set
oval:ssg-obj_sshd_enable_warning_banner_net:obj:1 oval:ssg-obj_sshd_enable_warning_banner_net_config_dir:obj:1
Limit Users' SSH Accessxccdf_org.ssgproject.content_rule_sshd_limit_user_access unknownCCE-90003-5

Limit Users' SSH Access

Rule IDxccdf_org.ssgproject.content_rule_sshd_limit_user_access
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_limit_user_access:def:1
Time2025-07-22T08:32:00+00:00
Severityunknown
Identifiers:

CCE-90003-5

References:
cis-csc11, 12, 14, 15, 16, 18, 3, 5
cobit5DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06
cui3.1.12
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7
iso27001-2013A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistAC-3, CM-6(a)
nist-csfPR.AC-4, PR.AC-6, PR.PT-3
pcidssReq-2.2.4
cis5.1.7
pcidss42.2.6, 2.2
Description
By default, the SSH configuration allows any user with an account to access the system. There are several options available to limit which users and group can access the system via SSH. It is recommended that at least one of the following options be leveraged: - AllowUsers variable gives the system administrator the option of allowing specific users to ssh into the system. The list consists of space separated user names. Numeric user IDs are not recognized with this variable. If a system administrator wants to restrict user access further by specifically allowing a user's access only from a particular host, the entry can be specified in the form of user@host. - AllowGroups variable gives the system administrator the option of allowing specific groups of users to ssh into the system. The list consists of space separated group names. Numeric group IDs are not recognized with this variable. - DenyUsers variable gives the system administrator the option of denying specific users to ssh into the system. The list consists of space separated user names. Numeric user IDs are not recognized with this variable. If a system administrator wants to restrict user access further by specifically denying a user's access from a particular host, the entry can be specified in the form of user@host. - DenyGroups variable gives the system administrator the option of denying specific groups of users to ssh into the system. The list consists of space separated group names. Numeric group IDs are not recognized with this variable.
Rationale
Specifying which accounts are allowed SSH access into the system reduces the possibility of unauthorized access to the system.
Warnings
warning  Automated remediation is not available for this configuration check because each system has unique user names and group names.
OVAL test results details

Check if there is an AllowUsers entry  oval:ssg-test_allow_user_is_configured:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_allow_user:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\/etc\/ssh\/sshd_config.*$(?i)^[ ]*AllowUsers[ ]+((?:[^ \n]+[ ]*)+)$1

Check if there is an AllowGroups entry  oval:ssg-test_allow_group_is_configured:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_allow_group:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/ssh/sshd_config.*$(?i)^[ ]*AllowGroups[ ]+((?:[^ \n]+[ ]*)+)$1

Check if there is a DenyUsers entry  oval:ssg-test_deny_user_is_configured:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_deny_user:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/ssh/sshd_config.*$(?i)^[ ]*DenyUsers[ ]+((?:[^ \n]+[ ]*)+)$1

Check if there is a DenyGroups entry  oval:ssg-test_deny_group_is_configured:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_deny_group:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/ssh/sshd_config.*$(?i)^[ ]*DenyGroups[ ]+((?:[^ \n]+[ ]*)+)$1
Set SSH Daemon LogLevel to VERBOSExccdf_org.ssgproject.content_rule_sshd_set_loglevel_verbose mediumCCE-86241-7

Set SSH Daemon LogLevel to VERBOSE

Rule IDxccdf_org.ssgproject.content_rule_sshd_set_loglevel_verbose
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_set_loglevel_verbose:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-86241-7

References:
nerc-cipCIP-007-3 R7.1
nistAC-17(a), AC-17(1), CM-6(a)
pcidssReq-2.2.4
os-srgSRG-OS-000032-GPOS-00013
cis5.1.15
pcidss42.2.6, 2.2
Description
The VERBOSE parameter configures the SSH daemon to record login and logout activity. To specify the log level in SSH, add or correct the following line in /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf:
LogLevel VERBOSE
Rationale
SSH provides several logging levels with varying amounts of verbosity. DEBUG is specifically not recommended other than strictly for debugging SSH communications since it provides so much data that it is difficult to identify important security information. INFO or VERBOSE level is the basic level that only records login activity of SSH users. In many situations, such as Incident Response, it is important to determine when a particular user was active on a system. The logout record can eliminate those users who disconnected, which helps narrow the field.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

LC_ALL=C sed -i "/^\s*LogLevel\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*LogLevel\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*LogLevel\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
    touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "LogLevel VERBOSE" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86241-7
  - NIST-800-53-AC-17(1)
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_loglevel_verbose

- name: Set SSH Daemon LogLevel to VERBOSE
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "LogLevel"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter LogLevel is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "LogLevel"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "LogLevel"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "LogLevel"| regex_escape }}\s+
      line: LogLevel VERBOSE
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86241-7
  - NIST-800-53-AC-17(1)
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_loglevel_verbose

- name: Set SSH Daemon LogLevel to VERBOSE - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86241-7
  - NIST-800-53-AC-17(1)
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_loglevel_verbose
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of LogLevel setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_set_loglevel_verbose:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_set_loglevel_verbose:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[ \t]*(?i)LogLevel(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

tests the value of LogLevel setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_set_loglevel_verbose_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_set_loglevel_verbose_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[ \t]*(?i)LogLevel(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

Verify that the value of LogLevel is present  oval:ssg-test_LogLevel_present_sshd_set_loglevel_verbose:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_sshd_set_loglevel_verbose:obj:1 of type textfilecontent54_object
Set
oval:ssg-obj_sshd_set_loglevel_verbose:obj:1 oval:ssg-obj_sshd_set_loglevel_verbose_config_dir:obj:1
Set SSH authentication attempt limitxccdf_org.ssgproject.content_rule_sshd_set_max_auth_tries mediumCCE-90071-2

Set SSH authentication attempt limit

Rule IDxccdf_org.ssgproject.content_rule_sshd_set_max_auth_tries
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_set_max_auth_tries:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-90071-2

References:
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
cis5.1.16
pcidss42.2.6, 2.2
Description
The MaxAuthTries parameter specifies the maximum number of authentication attempts permitted per connection. Once the number of failures reaches half this value, additional failures are logged. to set MaxAUthTries edit /etc/ssh/sshd_config as follows:
MaxAuthTries 4
        
Rationale
Setting the MaxAuthTries parameter to a low number will minimize the risk of successful brute force attacks to the SSH server.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

sshd_max_auth_tries_value='4'


mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

LC_ALL=C sed -i "/^\s*MaxAuthTries\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*MaxAuthTries\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*MaxAuthTries\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
    touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "MaxAuthTries $sshd_max_auth_tries_value" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90071-2
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_max_auth_tries
- name: XCCDF Value sshd_max_auth_tries_value # promote to variable
  set_fact:
    sshd_max_auth_tries_value: !!str 4
  tags:
    - always

- name: Set SSH authentication attempt limit
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "MaxAuthTries"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter MaxAuthTries is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "MaxAuthTries"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "MaxAuthTries"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "MaxAuthTries"| regex_escape }}\s+
      line: MaxAuthTries {{ sshd_max_auth_tries_value }}
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90071-2
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_max_auth_tries

- name: Set SSH authentication attempt limit - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90071-2
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_max_auth_tries
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

MaxAuthTries is configured  oval:ssg-test_sshd_max_auth_tries:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_sshd_max_auth_tries:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[\s]*(?i)MaxAuthTries[\s]+(\d+)[\s]*(?:#.*)?$1

tests the value of MaxAuthTries setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_set_max_auth_tries_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_set_max_auth_tries_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[ \t]*(?i)MaxAuthTries(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

Verify that the value of MaxAuthTries is present  oval:ssg-test_MaxAuthTries_present_sshd_set_max_auth_tries:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_sshd_set_max_auth_tries:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_sshd_max_auth_tries:obj:1 oval:ssg-obj_sshd_set_max_auth_tries_config_dir:obj:1
Set SSH MaxSessions limitxccdf_org.ssgproject.content_rule_sshd_set_max_sessions mediumCCE-89659-7

Set SSH MaxSessions limit

Rule IDxccdf_org.ssgproject.content_rule_sshd_set_max_sessions
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_set_max_sessions:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-89659-7

References:
cis5.1.18
pcidss42.2.6, 2.2
Description
The MaxSessions parameter specifies the maximum number of open sessions permitted from a given connection. To set MaxSessions edit /etc/ssh/sshd_config as follows:
MaxSessions 10
        
Rationale
To protect a system from denial of service due to a large number of concurrent sessions, use the rate limiting function of MaxSessions to protect availability of sshd logins and prevent overwhelming the daemon.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

var_sshd_max_sessions='10'


mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

LC_ALL=C sed -i "/^\s*MaxSessions\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*MaxSessions\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*MaxSessions\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
    touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "MaxSessions $var_sshd_max_sessions" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89659-7
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_max_sessions
- name: XCCDF Value var_sshd_max_sessions # promote to variable
  set_fact:
    var_sshd_max_sessions: !!str 10
  tags:
    - always

- name: Set SSH MaxSessions limit
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "MaxSessions"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter MaxSessions is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "MaxSessions"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "MaxSessions"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "MaxSessions"| regex_escape }}\s+
      line: MaxSessions {{ var_sshd_max_sessions }}
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89659-7
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_max_sessions

- name: Set SSH MaxSessions limit - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89659-7
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_max_sessions
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

MaxSessions is configured  oval:ssg-test_sshd_max_sessions:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_sshd_max_sessions:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/ssh/sshd_config^[\s]*(?i)MaxSessions[\s]+(\d+)[\s]*(?:#.*)?$1

tests the value of MaxSessions setting in the /etc/ssh/sshd_config.d file  oval:ssg-test_sshd_set_max_sessions_config_dir:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_set_max_sessions_config_dir:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/ssh/sshd_config.d.*\.conf$^[ \t]*(?i)MaxSessions(?-i)[ \t]+(.+?)[ \t]*(?:$|#)1

Verify that the value of MaxSessions is present  oval:ssg-test_MaxSessions_present_sshd_set_max_sessions:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_collection_obj_sshd_set_max_sessions:obj:1 of type textfilecontent54_object
Set
oval:ssg-object_sshd_max_sessions:obj:1 oval:ssg-obj_sshd_set_max_sessions_config_dir:obj:1
Ensure SSH MaxStartups is configuredxccdf_org.ssgproject.content_rule_sshd_set_maxstartups mediumCCE-89624-1

Ensure SSH MaxStartups is configured

Rule IDxccdf_org.ssgproject.content_rule_sshd_set_maxstartups
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_set_maxstartups:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-89624-1

References:
cis5.1.17
pcidss42.2.6, 2.2
Description
The MaxStartups parameter specifies the maximum number of concurrent unauthenticated connections to the SSH daemon. Additional connections will be dropped until authentication succeeds or the LoginGraceTime expires for a connection. To configure MaxStartups, you should add or edit the following line in the /etc/ssh/sshd_config file:
MaxStartups 10:30:60
        
Rationale
To protect a system from denial of service due to a large number of pending authentication connection attempts, use the rate limiting function of MaxStartups to protect availability of sshd logins and prevent overwhelming the daemon.

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

var_sshd_set_maxstartups='10:30:60'


mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

LC_ALL=C sed -i "/^\s*MaxStartups\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*MaxStartups\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
    
    LC_ALL=C sed -i "/^\s*MaxStartups\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
    touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "MaxStartups $var_sshd_set_maxstartups" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89624-1
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_maxstartups
- name: XCCDF Value var_sshd_set_maxstartups # promote to variable
  set_fact:
    var_sshd_set_maxstartups: !!str 10:30:60
  tags:
    - always

- name: Ensure SSH MaxStartups is configured
  block:

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: false
      regexp: (?i)(?i)^\s*{{ "MaxStartups"| regex_escape }}\s+
      state: absent

  - name: Check if /etc/ssh/sshd_config.d exists
    stat:
      path: /etc/ssh/sshd_config.d
    register: _etc_ssh_sshd_config_d_exists

  - name: Check if the parameter MaxStartups is present in /etc/ssh/sshd_config.d
    find:
      paths: /etc/ssh/sshd_config.d
      recurse: 'yes'
      follow: 'no'
      contains: (?i)^\s*{{ "MaxStartups"| regex_escape }}\s+
    register: _etc_ssh_sshd_config_d_has_parameter
    when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

  - name: Remove parameter from files in /etc/ssh/sshd_config.d
    lineinfile:
      path: '{{ item.path }}'
      create: false
      regexp: (?i)(?i)^\s*{{ "MaxStartups"| regex_escape }}\s+
      state: absent
    with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
    when: _etc_ssh_sshd_config_d_has_parameter.matched

  - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    lineinfile:
      path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      create: true
      regexp: (?i)(?i)^\s*{{ "MaxStartups"| regex_escape }}\s+
      line: MaxStartups {{ var_sshd_set_maxstartups }}
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89624-1
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_maxstartups

- name: Ensure SSH MaxStartups is configured - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
  ansible.builtin.file:
    path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    mode: '0600'
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89624-1
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_set_maxstartups
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

SSH MaxStartups start parameter is less than or equal to the expected value  oval:ssg-tst_maxstartups_start_parameter:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_config_maxstartups_first_parameter:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/(ssh|ssh/sshd_config.d)(sshd_config|.*\.conf)$(?i)^\s*MaxStartups\s+(\d+):\d+:\d+\s*$1

SSH MaxStartups rate parameter is greater than or equal to the expected value  oval:ssg-tst_maxstartups_rate_parameter:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_config_maxstartups_second_parameter:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/(ssh|ssh/sshd_config.d)(sshd_config|.*\.conf)$(?i)^\s*MaxStartups\s+\d+:(\d+):\d+\s*$1

SSH MaxStartups full parameter is less than or equal to the expected value  oval:ssg-tst_maxstartups_full_parameter:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_config_maxstartups_third_parameter:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/etc/(ssh|ssh/sshd_config.d)(sshd_config|.*\.conf)$(?i)^\s*MaxStartups\s+\d+:\d+:(\d+)\s*$1
Use Only Strong Key Exchange algorithmsxccdf_org.ssgproject.content_rule_sshd_use_strong_kex mediumCCE-87009-7

Use Only Strong Key Exchange algorithms

Rule IDxccdf_org.ssgproject.content_rule_sshd_use_strong_kex
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_use_strong_kex:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-87009-7

References:
pcidssReq-2.3
cis5.1.5
pcidss42.2.7, 2.2
Description
Limit the Key Exchange to strong algorithms. The following line in /etc/ssh/sshd_config demonstrates use of those:
KexAlgorithms -diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1
        
Rationale
Key exchange is any method in cryptography by which cryptographic keys are exchanged between two parties, allowing use of a cryptographic algorithm. If the sender and receiver wish to exchange encrypted messages, each must be equipped to encrypt messages to be sent and decrypt messages received

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

sshd_strong_kex='-diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1'


if [ -e "/etc/ssh/sshd_config" ] ; then
    
    LC_ALL=C sed -i "/^\s*KexAlgorithms\s\+/Id" "/etc/ssh/sshd_config"
else
    touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"

cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert at the beginning of the file
printf '%s\n' "KexAlgorithms $sshd_strong_kex" > "/etc/ssh/sshd_config"
cat "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87009-7
  - PCI-DSS-Req-2.3
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.7
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_use_strong_kex
- name: XCCDF Value sshd_strong_kex # promote to variable
  set_fact:
    sshd_strong_kex: !!str -diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1
  tags:
    - always

- name: Use Only Strong Key Exchange algorithms
  block:

  - name: Check for duplicate values
    lineinfile:
      path: /etc/ssh/sshd_config
      create: true
      regexp: (?i)(?i)^\s*KexAlgorithms\s+
      state: absent
    check_mode: true
    changed_when: false
    register: dupes

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: true
      regexp: (?i)(?i)^\s*KexAlgorithms\s+
      state: absent
    when: dupes.found is defined and dupes.found > 1

  - name: Insert correct line to /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: true
      regexp: (?i)(?i)^\s*KexAlgorithms\s+
      line: KexAlgorithms {{ sshd_strong_kex }}
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87009-7
  - PCI-DSS-Req-2.3
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.7
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_use_strong_kex
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of KexAlgorithms setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_use_strong_kex:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_use_strong_kex:obj:1 of type variable_object
Var ref
oval:ssg-var_sshd_config_kex:var:1
Use Only Strong MACsxccdf_org.ssgproject.content_rule_sshd_use_strong_macs mediumCCE-86792-9

Use Only Strong MACs

Rule IDxccdf_org.ssgproject.content_rule_sshd_use_strong_macs
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-sshd_use_strong_macs:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-86792-9

References:
nistAC-17 (2)
os-srgSRG-OS-000250-GPOS-00093
cis5.1.6
Description
Limit the MACs to strong hash algorithms. The following line in /etc/ssh/sshd_config demonstrates use of those MACs:
MACs -hmac-md5,hmac-md5-96,hmac-ripemd160,hmac-sha1-96,umac-64@openssh.com,hmac-md5-etm@openssh.com,hmac-md5-96-etm@openssh.com,hmac-ripemd160-etm@openssh.com,hmac-sha1-96-etm@openssh.com,umac-64-etm@openssh.com
        
Rationale
MD5 and 96-bit MAC algorithms are considered weak and have been shown to increase exploitability in SSH downgrade attacks. Weak algorithms continue to have a great deal of attention as a weak spot that can be exploited with expanded computing power. An attacker that breaks the algorithm could take advantage of a MiTM position to decrypt the SSH tunnel and capture credentials and information

# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel; then

sshd_strong_macs='-hmac-md5,hmac-md5-96,hmac-ripemd160,hmac-sha1-96,umac-64@openssh.com,hmac-md5-etm@openssh.com,hmac-md5-96-etm@openssh.com,hmac-ripemd160-etm@openssh.com,hmac-sha1-96-etm@openssh.com,umac-64-etm@openssh.com'


# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^MACs")

# shellcheck disable=SC2059
printf -v formatted_output "%s %s" "$stripped_key" "$sshd_strong_macs"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^MACs\\>" "/etc/ssh/sshd_config"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^MACs\\>.*/$escaped_formatted_output/gi" "/etc/ssh/sshd_config"
else
    if [[ -s "/etc/ssh/sshd_config" ]] && [[ -n "$(tail -c 1 -- "/etc/ssh/sshd_config" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/ssh/sshd_config"
    fi
    cce="CCE-86792-9"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/ssh/sshd_config" >> "/etc/ssh/sshd_config"
    printf '%s\n' "$formatted_output" >> "/etc/ssh/sshd_config"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86792-9
  - NIST-800-53-AC-17 (2)
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_use_strong_macs
- name: XCCDF Value sshd_strong_macs # promote to variable
  set_fact:
    sshd_strong_macs: !!str -hmac-md5,hmac-md5-96,hmac-ripemd160,hmac-sha1-96,umac-64@openssh.com,hmac-md5-etm@openssh.com,hmac-md5-96-etm@openssh.com,hmac-ripemd160-etm@openssh.com,hmac-sha1-96-etm@openssh.com,umac-64-etm@openssh.com
  tags:
    - always

- name: Use Only Strong MACs
  block:

  - name: Check for duplicate values
    lineinfile:
      path: /etc/ssh/sshd_config
      create: true
      regexp: (?i)(?i)^\s*MACs\s+
      state: absent
    check_mode: true
    changed_when: false
    register: dupes

  - name: Deduplicate values from /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: true
      regexp: (?i)(?i)^\s*MACs\s+
      state: absent
    when: dupes.found is defined and dupes.found > 1

  - name: Insert correct line to /etc/ssh/sshd_config
    lineinfile:
      path: /etc/ssh/sshd_config
      create: true
      regexp: (?i)(?i)^\s*MACs\s+
      line: MACs {{ sshd_strong_macs }}
      state: present
      insertbefore: BOF
      validate: /usr/sbin/sshd -t -f %s
  when: '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86792-9
  - NIST-800-53-AC-17 (2)
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_use_strong_macs
OVAL test results details

Verify if Profile set Value sshd_required as not required  oval:ssg-test_sshd_not_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is removed  oval:ssg-test_package_openssh-server_removed:tst:1  false

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

Verify if Profile set Value sshd_required as required  oval:ssg-test_sshd_required:tst:1  false

Following items have been found on the system:
Result of item-state comparisonVar refValue
falseoval:ssg-sshd_required:var:10

Verify if Value of sshd_required is the default  oval:ssg-test_sshd_requirement_unset:tst:1  true

Following items have been found on the system:
Result of item-state comparisonVar refValue
trueoval:ssg-sshd_required:var:10

package openssh-server is installed  oval:ssg-test_package_openssh-server_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedopenssh-serverx86_64(none)7.el10_09.9p10:9.9p1-7.el10_0199e2f91fd431d51openssh-server-0:9.9p1-7.el10_0.x86_64

tests the value of MACs setting in the /etc/ssh/sshd_config file  oval:ssg-test_sshd_use_strong_macs:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_sshd_use_strong_macs:obj:1 of type variable_object
Var ref
oval:ssg-var_sshd_config_strong_macs:var:1
Verify Group Who Owns SSH Server config filexccdf_org.ssgproject.content_rule_file_groupowner_sshd_config mediumCCE-86992-5

Verify Group Who Owns SSH Server config file

Rule IDxccdf_org.ssgproject.content_rule_file_groupowner_sshd_config
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupowner_sshd_config:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-86992-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistAC-17(a), CM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis5.1.1
Description
To properly set the group owner of /etc/ssh/sshd_config, run the command:
$ sudo chgrp root /etc/ssh/sshd_config
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
OVAL test results details

Testing group ownership of /etc/ssh/sshd_config  oval:ssg-test_file_groupowner_sshd_config_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupowner_sshd_config_0:obj:1 of type file_object
FilepathFilterFilter
/etc/ssh/sshd_configoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupowner_sshd_config_0_0:ste:1
Verify Group Ownership on SSH Server Private *_key Key Filesxccdf_org.ssgproject.content_rule_file_groupownership_sshd_private_key mediumCCE-90288-2

Verify Group Ownership on SSH Server Private *_key Key Files

Rule IDxccdf_org.ssgproject.content_rule_file_groupownership_sshd_private_key
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupownership_sshd_private_key:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-90288-2

References:
anssiR50
cis5.1.2
Description
SSH server private keys, files that match the /etc/ssh/*_key glob, must be group-owned by root group.
Rationale
If an unauthorized user obtains the private SSH host key file, the host could be impersonated.
Warnings
warning  Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
OVAL test results details

Testing group ownership of /etc/ssh/  oval:ssg-test_file_groupownership_sshd_private_key_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_sshd_private_key_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/ssh^.*_key$oval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_sshd_private_key_0_0:ste:1
Verify Group Ownership on SSH Server Public *.pub Key Filesxccdf_org.ssgproject.content_rule_file_groupownership_sshd_pub_key mediumCCE-90469-8

Verify Group Ownership on SSH Server Public *.pub Key Files

Rule IDxccdf_org.ssgproject.content_rule_file_groupownership_sshd_pub_key
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupownership_sshd_pub_key:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-90469-8

References:
anssiR50
cis5.1.3
Description
SSH server public keys, files that match the /etc/ssh/*.pub glob, must be group-owned by root group.
Rationale
If a public host key file is modified by an unauthorized user, the SSH service may be compromised.
Warnings
warning  Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
OVAL test results details

Testing group ownership of /etc/ssh/  oval:ssg-test_file_groupownership_sshd_pub_key_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_sshd_pub_key_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/ssh^.*\.pub$oval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_sshd_pub_key_0_0:ste:1
Verify Owner on SSH Server config filexccdf_org.ssgproject.content_rule_file_owner_sshd_config mediumCCE-89829-6

Verify Owner on SSH Server config file

Rule IDxccdf_org.ssgproject.content_rule_file_owner_sshd_config
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_owner_sshd_config:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-89829-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistAC-17(a), CM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis5.1.1
Description
To properly set the owner of /etc/ssh/sshd_config, run the command:
$ sudo chown root /etc/ssh/sshd_config 
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
OVAL test results details

Testing user ownership of /etc/ssh/sshd_config  oval:ssg-test_file_owner_sshd_config_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_owner_sshd_config_0:obj:1 of type file_object
FilepathFilterFilter
/etc/ssh/sshd_configoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_owner_sshd_config_0_0:ste:1
Verify Ownership on SSH Server Private *_key Key Filesxccdf_org.ssgproject.content_rule_file_ownership_sshd_private_key mediumCCE-90624-8

Verify Ownership on SSH Server Private *_key Key Files

Rule IDxccdf_org.ssgproject.content_rule_file_ownership_sshd_private_key
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_ownership_sshd_private_key:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-90624-8

References:
anssiR50
cis5.1.2
Description
SSH server private keys, files that match the /etc/ssh/*_key glob, must be owned by root user.
Rationale
If an unauthorized user obtains the private SSH host key file, the host could be impersonated.
Warnings
warning  Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
OVAL test results details

Testing user ownership of /etc/ssh/  oval:ssg-test_file_ownership_sshd_private_key_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_sshd_private_key_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/ssh^.*_key$oval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_sshd_private_key_0_0:ste:1
Verify Ownership on SSH Server Public *.pub Key Filesxccdf_org.ssgproject.content_rule_file_ownership_sshd_pub_key mediumCCE-87297-8

Verify Ownership on SSH Server Public *.pub Key Files

Rule IDxccdf_org.ssgproject.content_rule_file_ownership_sshd_pub_key
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_ownership_sshd_pub_key:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-87297-8

References:
anssiR50
cis5.1.3
Description
SSH server public keys, files that match the /etc/ssh/*.pub glob, must be owned by root user.
Rationale
If a public host key file is modified by an unauthorized user, the SSH service may be compromised.
Warnings
warning  Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
OVAL test results details

Testing user ownership of /etc/ssh/  oval:ssg-test_file_ownership_sshd_pub_key_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_sshd_pub_key_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/ssh^.*\.pub$oval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_sshd_pub_key_0_0:ste:1
Verify Permissions on SSH Server config filexccdf_org.ssgproject.content_rule_file_permissions_sshd_config mediumCCE-86264-9

Verify Permissions on SSH Server config file

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_sshd_config
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_sshd_config:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-86264-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistAC-17(a), CM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis5.1.1
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/ssh/sshd_config, run the command:
$ sudo chmod 0600 /etc/ssh/sshd_config
Rationale
Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
OVAL test results details

Testing mode of /etc/ssh/sshd_config  oval:ssg-test_file_permissions_sshd_config_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_sshd_config_0:obj:1 of type file_object
FilepathFilterFilter
/etc/ssh/sshd_configoval:ssg-exclude_symlinks__sshd_config:ste:1oval:ssg-state_file_permissions_sshd_config_0_mode_0600or_stricter_:ste:1
Verify Permissions on SSH Server Private *_key Key Filesxccdf_org.ssgproject.content_rule_file_permissions_sshd_private_key mediumCCE-88018-7

Verify Permissions on SSH Server Private *_key Key Files

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_sshd_private_key
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_sshd_private_key:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-88018-7

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.1.13, 3.13.10
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistAC-17(a), CM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-2.2.4
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis5.1.2
pcidss42.2.6, 2.2
Description
SSH server private keys - files that match the /etc/ssh/*_key glob, have to have restricted permissions. If those files are owned by the root user and the root group, they have to have the 0600 permission or stricter.
Rationale
If an unauthorized user obtains the private SSH host key file, the host could be impersonated.
Warnings
warning  Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
OVAL test results details

No keys that have unsafe ownership/permissions combination exist  oval:ssg-test_no_offending_keys:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_offending_keys:obj:1 of type file_object
PathFilenameFilterFilter
/etc/ssh.*_key$oval:ssg-exclude_symlinks__sshd_private_key:ste:1oval:ssg-filter_ssh_key_owner_root:ste:1
Verify Permissions on SSH Server Public *.pub Key Filesxccdf_org.ssgproject.content_rule_file_permissions_sshd_pub_key mediumCCE-87454-5

Verify Permissions on SSH Server Public *.pub Key Files

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_sshd_pub_key
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_sshd_pub_key:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-87454-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.1.13, 3.13.10
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistAC-17(a), CM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-2.2.4
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis5.1.3
pcidss42.2.6, 2.2
Description
To properly set the permissions of /etc/ssh/*.pub, run the command:
$ sudo chmod 0644 /etc/ssh/*.pub
Rationale
If a public host key file is modified by an unauthorized user, the SSH service may be compromised.
Warnings
warning  Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
OVAL test results details

Testing mode of /etc/ssh/  oval:ssg-test_file_permissions_sshd_pub_key_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_sshd_pub_key_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/ssh^.*\.pub$oval:ssg-exclude_symlinks__sshd_pub_key:ste:1oval:ssg-state_file_permissions_sshd_pub_key_0_mode_0644or_stricter_:ste:1
Disable Graphical Environment Startup By Setting Default Targetxccdf_org.ssgproject.content_rule_xwindows_runlevel_target mediumCCE-89266-1

Disable Graphical Environment Startup By Setting Default Target

Rule IDxccdf_org.ssgproject.content_rule_xwindows_runlevel_target
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-xwindows_runlevel_target:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-89266-1

References:
cis-csc12, 15, 8
cobit5APO13.01, DSS01.04, DSS05.02, DSS05.03
isa-62443-20094.3.3.6.6
isa-62443-2013SR 1.13, SR 2.6, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.13.1.1, A.13.2.1, A.14.1.3, A.6.2.1, A.6.2.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
cis2.1.20
Description
Systems that do not require a graphical user interface should only boot by default into multi-user.target mode. This prevents accidental booting of the system into a graphical.target mode. Setting the system's default target to multi-user.target will prevent automatic startup of the graphical environment. To do so, run:
$ systemctl set-default multi-user.target
You should see the following output:
Removed symlink /etc/systemd/system/default.target.
Created symlink from /etc/systemd/system/default.target to /usr/lib/systemd/system/multi-user.target.
Rationale
Services that are not required for system and application processes must not be active to decrease the attack surface of the system.
OVAL test results details

default.target systemd softlink exists  oval:ssg-test_disable_xwindows_runlevel_target:tst:1  true

Following items have been found on the system:
Result of item-state comparisonFilepathCanonical path
true/etc/systemd/system/default.target/usr/lib/systemd/system/multi-user.target
Record Events that Modify the System's Discretionary Access Controls - chmodxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_chmod mediumCCE-90466-4

Record Events that Modify the System's Discretionary Access Controls - chmod

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_chmod
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_chmod:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90466-4

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="chmod"
	KEY="perm_mod"
	SYSCALL_GROUPING="chmod fchmod fchmodat"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90466-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit chmod tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-90466-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for chmod for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - chmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of chmod in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - chmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of chmod in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-90466-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for chmod for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - chmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of chmod in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - chmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of chmod in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-90466-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit chmod  oval:ssg-test_32bit_ardm_chmod_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_chmod_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+chmod[\s]+|([\s]+|[,])chmod([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit chmod  oval:ssg-test_64bit_ardm_chmod_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_chmod_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+chmod[\s]+|([\s]+|[,])chmod([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit chmod  oval:ssg-test_32bit_ardm_chmod_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_chmod_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+chmod[\s]+|([\s]+|[,])chmod([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit chmod  oval:ssg-test_64bit_ardm_chmod_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_chmod_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+chmod[\s]+|([\s]+|[,])chmod([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - chownxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_chown mediumCCE-89540-9

Record Events that Modify the System's Discretionary Access Controls - chown

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_chown
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_chown:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89540-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="chown"
	KEY="perm_mod"
	SYSCALL_GROUPING="chown fchown fchownat lchown"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89540-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit chown tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-89540-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for chown for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - chown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of chown in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - chown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of chown in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-89540-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for chown for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - chown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of chown in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - chown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of chown in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-89540-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit chown  oval:ssg-test_32bit_ardm_chown_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_chown_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+chown[\s]+|([\s]+|[,])chown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit chown  oval:ssg-test_64bit_ardm_chown_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_chown_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+chown[\s]+|([\s]+|[,])chown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit chown  oval:ssg-test_32bit_ardm_chown_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_chown_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+chown[\s]+|([\s]+|[,])chown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit chown  oval:ssg-test_64bit_ardm_chown_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_chown_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+chown[\s]+|([\s]+|[,])chown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - fchmodxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchmod mediumCCE-88200-1

Record Events that Modify the System's Discretionary Access Controls - fchmod

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchmod
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_fchmod:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88200-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=unset -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="fchmod"
	KEY="perm_mod"
	SYSCALL_GROUPING="chmod fchmod fchmodat"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88200-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fchmod tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-88200-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmod for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of fchmod in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of fchmod in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88200-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmod for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of fchmod in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of fchmod in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-88200-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit fchmod  oval:ssg-test_32bit_ardm_fchmod_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchmod_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchmod[\s]+|([\s]+|[,])fchmod([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit fchmod  oval:ssg-test_64bit_ardm_fchmod_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchmod_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchmod[\s]+|([\s]+|[,])fchmod([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit fchmod  oval:ssg-test_32bit_ardm_fchmod_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchmod_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchmod[\s]+|([\s]+|[,])fchmod([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit fchmod  oval:ssg-test_64bit_ardm_fchmod_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchmod_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchmod[\s]+|([\s]+|[,])fchmod([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - fchmodatxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchmodat mediumCCE-89356-0

Record Events that Modify the System's Discretionary Access Controls - fchmodat

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchmodat
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_fchmodat:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89356-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="fchmodat"
	KEY="perm_mod"
	SYSCALL_GROUPING="chmod fchmod fchmodat fchmodat2"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89356-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fchmodat tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-89356-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmodat for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmodat
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat
      - fchmodat2

  - name: Check existence of fchmodat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmodat
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat
      - fchmodat2

  - name: Check existence of fchmodat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89356-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmodat for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmodat
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat
      - fchmodat2

  - name: Check existence of fchmodat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmodat
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat
      - fchmodat2

  - name: Check existence of fchmodat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-89356-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit fchmodat  oval:ssg-test_32bit_ardm_fchmodat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchmodat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchmodat[\s]+|([\s]+|[,])fchmodat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit fchmodat  oval:ssg-test_64bit_ardm_fchmodat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchmodat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchmodat[\s]+|([\s]+|[,])fchmodat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit fchmodat  oval:ssg-test_32bit_ardm_fchmodat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchmodat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchmodat[\s]+|([\s]+|[,])fchmodat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit fchmodat  oval:ssg-test_64bit_ardm_fchmodat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchmodat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchmodat[\s]+|([\s]+|[,])fchmodat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - fchmodat2xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchmodat2 mediumCCE-86535-2

Record Events that Modify the System's Discretionary Access Controls - fchmodat2

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchmodat2
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_fchmodat2:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-86535-2

References:
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S fchmodat2 -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmodat2 -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchmodat2 -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmodat2 -F auid>=1000 -F auid!=unset -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="fchmodat2"
	KEY="perm_mod"
	SYSCALL_GROUPING="chmod fchmod fchmodat fchmodat2"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86535-2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat2
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fchmodat2 tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-86535-2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat2
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmodat2 for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmodat2
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat
      - fchmodat2

  - name: Check existence of fchmodat2 in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmodat2
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat
      - fchmodat2

  - name: Check existence of fchmodat2 in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86535-2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat2
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmodat2 for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmodat2
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat
      - fchmodat2

  - name: Check existence of fchmodat2 in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchmodat2
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat
      - fchmodat2

  - name: Check existence of fchmodat2 in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-86535-2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat2
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit fchmodat2  oval:ssg-test_32bit_ardm_fchmodat2_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchmodat2_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchmodat2[\s]+|([\s]+|[,])fchmodat2([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit fchmodat2  oval:ssg-test_64bit_ardm_fchmodat2_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchmodat2_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchmodat2[\s]+|([\s]+|[,])fchmodat2([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit fchmodat2  oval:ssg-test_32bit_ardm_fchmodat2_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchmodat2_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchmodat2[\s]+|([\s]+|[,])fchmodat2([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit fchmodat2  oval:ssg-test_64bit_ardm_fchmodat2_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchmodat2_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchmodat2[\s]+|([\s]+|[,])fchmodat2([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - fchownxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchown mediumCCE-90685-9

Record Events that Modify the System's Discretionary Access Controls - fchown

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchown
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_fchown:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90685-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=unset -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="fchown"
	KEY="perm_mod"
	SYSCALL_GROUPING="chown fchown fchownat lchown"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90685-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fchown tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-90685-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchown for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchown in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchown in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90685-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchown for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchown in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchown in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-90685-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit fchown  oval:ssg-test_32bit_ardm_fchown_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchown_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchown[\s]+|([\s]+|[,])fchown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit fchown  oval:ssg-test_64bit_ardm_fchown_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchown_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchown[\s]+|([\s]+|[,])fchown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit fchown  oval:ssg-test_32bit_ardm_fchown_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchown_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchown[\s]+|([\s]+|[,])fchown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit fchown  oval:ssg-test_64bit_ardm_fchown_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchown_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchown[\s]+|([\s]+|[,])fchown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - fchownatxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchownat mediumCCE-90651-1

Record Events that Modify the System's Discretionary Access Controls - fchownat

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchownat
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_fchownat:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90651-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=unset -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="fchownat"
	KEY="perm_mod"
	SYSCALL_GROUPING="chown fchown fchownat lchown"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90651-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchownat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fchownat tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-90651-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchownat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchownat for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchownat
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchownat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchownat
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchownat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90651-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchownat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchownat for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchownat
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchownat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fchownat
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchownat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-90651-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchownat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit fchownat  oval:ssg-test_32bit_ardm_fchownat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchownat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchownat[\s]+|([\s]+|[,])fchownat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit fchownat  oval:ssg-test_64bit_ardm_fchownat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchownat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchownat[\s]+|([\s]+|[,])fchownat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit fchownat  oval:ssg-test_32bit_ardm_fchownat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fchownat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fchownat[\s]+|([\s]+|[,])fchownat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit fchownat  oval:ssg-test_64bit_ardm_fchownat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fchownat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fchownat[\s]+|([\s]+|[,])fchownat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - fremovexattrxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fremovexattr mediumCCE-88352-0

Record Events that Modify the System's Discretionary Access Controls - fremovexattr

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fremovexattr
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_fremovexattr:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88352-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root.

If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S fremovexattr -F auid=0 -F key=perm_mod


If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S fremovexattr -F auid=0 -F key=perm_mod


If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S fremovexattr -F auid=0 -F key=perm_mod


If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S fremovexattr -F auid=0 -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="fremovexattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88352-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fremovexattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-88352-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fremovexattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fremovexattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fremovexattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88352-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fremovexattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fremovexattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fremovexattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-88352-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit fremovexattr  oval:ssg-test_32bit_ardm_fremovexattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fremovexattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fremovexattr[\s]+|([\s]+|[,])fremovexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit fremovexattr  oval:ssg-test_64bit_ardm_fremovexattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fremovexattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fremovexattr[\s]+|([\s]+|[,])fremovexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit fremovexattr  oval:ssg-test_32bit_ardm_fremovexattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fremovexattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fremovexattr[\s]+|([\s]+|[,])fremovexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit fremovexattr  oval:ssg-test_64bit_ardm_fremovexattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fremovexattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fremovexattr[\s]+|([\s]+|[,])fremovexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - fsetxattrxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fsetxattr mediumCCE-89370-1

Record Events that Modify the System's Discretionary Access Controls - fsetxattr

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fsetxattr
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_fsetxattr:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89370-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S fsetxattr -F auid=0 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S fsetxattr -F auid=0 -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S fsetxattr -F auid=0 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S fsetxattr -F auid=0 -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="fsetxattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89370-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fsetxattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-89370-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fsetxattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fsetxattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fsetxattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89370-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fsetxattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fsetxattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fsetxattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-89370-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit fsetxattr  oval:ssg-test_32bit_ardm_fsetxattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fsetxattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fsetxattr[\s]+|([\s]+|[,])fsetxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit fsetxattr  oval:ssg-test_64bit_ardm_fsetxattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fsetxattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fsetxattr[\s]+|([\s]+|[,])fsetxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit fsetxattr  oval:ssg-test_32bit_ardm_fsetxattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_fsetxattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+fsetxattr[\s]+|([\s]+|[,])fsetxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit fsetxattr  oval:ssg-test_64bit_ardm_fsetxattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_fsetxattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+fsetxattr[\s]+|([\s]+|[,])fsetxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - lchownxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_lchown mediumCCE-88243-1

Record Events that Modify the System's Discretionary Access Controls - lchown

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_lchown
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_lchown:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88243-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="lchown"
	KEY="perm_mod"
	SYSCALL_GROUPING="chown fchown fchownat lchown"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88243-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit lchown tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-88243-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lchown for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of lchown in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of lchown in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-88243-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lchown for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of lchown in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of lchown in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-88243-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit lchown  oval:ssg-test_32bit_ardm_lchown_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_lchown_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+lchown[\s]+|([\s]+|[,])lchown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit lchown  oval:ssg-test_64bit_ardm_lchown_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_lchown_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+lchown[\s]+|([\s]+|[,])lchown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit lchown  oval:ssg-test_32bit_ardm_lchown_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_lchown_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+lchown[\s]+|([\s]+|[,])lchown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit lchown  oval:ssg-test_64bit_ardm_lchown_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_lchown_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+lchown[\s]+|([\s]+|[,])lchown([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - lremovexattrxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_lremovexattr mediumCCE-90100-9

Record Events that Modify the System's Discretionary Access Controls - lremovexattr

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_lremovexattr
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_lremovexattr:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90100-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root.

If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S lremovexattr -F auid=0 -F key=perm_mod


If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S lremovexattr -F auid=0 -F key=perm_mod


If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S lremovexattr -F auid=0 -F key=perm_mod


If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S lremovexattr -F auid=0 -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="lremovexattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90100-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit lremovexattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-90100-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lremovexattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lremovexattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lremovexattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90100-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lremovexattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lremovexattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lremovexattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-90100-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit lremovexattr  oval:ssg-test_32bit_ardm_lremovexattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_lremovexattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+lremovexattr[\s]+|([\s]+|[,])lremovexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit lremovexattr  oval:ssg-test_64bit_ardm_lremovexattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_lremovexattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+lremovexattr[\s]+|([\s]+|[,])lremovexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit lremovexattr  oval:ssg-test_32bit_ardm_lremovexattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_lremovexattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+lremovexattr[\s]+|([\s]+|[,])lremovexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit lremovexattr  oval:ssg-test_64bit_ardm_lremovexattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_lremovexattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+lremovexattr[\s]+|([\s]+|[,])lremovexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - lsetxattrxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_lsetxattr mediumCCE-88052-6

Record Events that Modify the System's Discretionary Access Controls - lsetxattr

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_lsetxattr
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_lsetxattr:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88052-6

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S lsetxattr -F auid=0 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S lsetxattr -F auid=0 -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S lsetxattr -F auid=0 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S lsetxattr -F auid=0 -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="lsetxattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88052-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit lsetxattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-88052-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lsetxattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lsetxattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lsetxattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88052-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lsetxattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lsetxattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lsetxattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-88052-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit lsetxattr  oval:ssg-test_32bit_ardm_lsetxattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_lsetxattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+lsetxattr[\s]+|([\s]+|[,])lsetxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit lsetxattr  oval:ssg-test_64bit_ardm_lsetxattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_lsetxattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+lsetxattr[\s]+|([\s]+|[,])lsetxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit lsetxattr  oval:ssg-test_32bit_ardm_lsetxattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_lsetxattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+lsetxattr[\s]+|([\s]+|[,])lsetxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit lsetxattr  oval:ssg-test_64bit_ardm_lsetxattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_lsetxattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+lsetxattr[\s]+|([\s]+|[,])lsetxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - removexattrxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_removexattr mediumCCE-89677-9

Record Events that Modify the System's Discretionary Access Controls - removexattr

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_removexattr
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_removexattr:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89677-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root.

If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S removexattr -F auid=0 -F key=perm_mod


If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S removexattr -F auid=0 -F key=perm_mod


If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S removexattr -F auid=0 -F key=perm_mod


If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S removexattr -F auid=0 -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="removexattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89677-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_removexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit removexattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-89677-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_removexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for removexattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - removexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of removexattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - removexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of removexattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89677-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_removexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for removexattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - removexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of removexattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - removexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of removexattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-89677-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_removexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit removexattr  oval:ssg-test_32bit_ardm_removexattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_removexattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+removexattr[\s]+|([\s]+|[,])removexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit removexattr  oval:ssg-test_64bit_ardm_removexattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_removexattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+removexattr[\s]+|([\s]+|[,])removexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit removexattr  oval:ssg-test_32bit_ardm_removexattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_removexattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+removexattr[\s]+|([\s]+|[,])removexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit removexattr  oval:ssg-test_64bit_ardm_removexattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_removexattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+removexattr[\s]+|([\s]+|[,])removexattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Discretionary Access Controls - setxattrxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_setxattr mediumCCE-89571-4

Record Events that Modify the System's Discretionary Access Controls - setxattr

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_dac_modification_setxattr
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_dac_modification_setxattr:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89571-4

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000466-GPOS-00210, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235
anssiR73
cis6.3.3.9
pcidss410.3.4, 10.3
Description
At a minimum, the audit system should collect file permission changes for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr -F auid=0 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr -F auid=0 -F key=perm_mod
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr -F auid=0 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr -F auid=0 -F key=perm_mod
Rationale
The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="setxattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89571-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_setxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit setxattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-89571-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_setxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for setxattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - setxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of setxattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - setxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of setxattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89571-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_setxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for setxattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - setxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of setxattr in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
    set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - setxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of setxattr in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=perm_mod
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-89571-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_setxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit setxattr  oval:ssg-test_32bit_ardm_setxattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_setxattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+setxattr[\s]+|([\s]+|[,])setxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit setxattr  oval:ssg-test_64bit_ardm_setxattr_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_setxattr_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+setxattr[\s]+|([\s]+|[,])setxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit setxattr  oval:ssg-test_32bit_ardm_setxattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_setxattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+setxattr[\s]+|([\s]+|[,])setxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit setxattr  oval:ssg-test_64bit_ardm_setxattr_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_setxattr_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+setxattr[\s]+|([\s]+|[,])setxattr([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Any Attempts to Run chaclxccdf_org.ssgproject.content_rule_audit_rules_execution_chacl mediumCCE-88467-6

Record Any Attempts to Run chacl

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_execution_chacl
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_execution_chacl:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88467-6

References:
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
cis6.3.3.17
Description
At a minimum, the audit system should collect the execution of privileged commands for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add a line of the following form to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add a line of the following form to /etc/audit/audit.rules, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
Rationale
Without generating audit records that are specific to the security and mission needs of the organization, it would be difficult to establish, correlate, and investigate the events relating to an incident or identify those responsible for one. Audit records can be generated from various components within the information system (e.g., module or policy filter).

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Retrieve hardware architecture of the underlying system
OTHER_FILTERS="-F path=/usr/bin/chacl -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""


[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
  ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
  # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88467-6
  - audit_rules_execution_chacl
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Any Attempts to Run chacl - Set architecture for audit /usr/bin/chacl
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-88467-6
  - audit_rules_execution_chacl
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Any Attempts to Run chacl - Perform remediation of Audit rules for
    /usr/bin/chacl 32 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chacl -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/bin/chacl
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chacl -F perm=x -F auid>=1000
        -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/bin/chacl
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88467-6
  - audit_rules_execution_chacl
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Any Attempts to Run chacl - Perform remediation of Audit rules for
    /usr/bin/chacl 64 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chacl -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/bin/chacl
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chacl -F perm=x -F auid>=1000
        -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/bin/chacl
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-88467-6
  - audit_rules_execution_chacl
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules chacl  oval:ssg-test_audit_rules_execution_chacl_augenrules_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_chacl_augenrules_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/bin\/chacl(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules chacl  oval:ssg-test_audit_rules_execution_chacl_augenrules_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_chacl_augenrules_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/bin\/chacl(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl chacl  oval:ssg-test_audit_rules_execution_chacl_auditctl_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_chacl_auditctl_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/bin\/chacl(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl chacl  oval:ssg-test_audit_rules_execution_chacl_auditctl_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_chacl_auditctl_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/bin\/chacl(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Any Attempts to Run setfaclxccdf_org.ssgproject.content_rule_audit_rules_execution_setfacl mediumCCE-87662-3

Record Any Attempts to Run setfacl

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_execution_setfacl
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_execution_setfacl:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-87662-3

References:
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215
app-srg-ctrSRG-APP-000495-CTR-001235
cis6.3.3.16
Description
At a minimum, the audit system should collect the execution of privileged commands for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add a line of the following form to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add a line of the following form to /etc/audit/audit.rules, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
Rationale
Without generating audit records that are specific to the security and mission needs of the organization, it would be difficult to establish, correlate, and investigate the events relating to an incident or identify those responsible for one. Audit records can be generated from various components within the information system (e.g., module or policy filter).

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Retrieve hardware architecture of the underlying system
OTHER_FILTERS="-F path=/usr/bin/setfacl -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""


[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
  ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
  # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87662-3
  - audit_rules_execution_setfacl
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Any Attempts to Run setfacl - Set architecture for audit /usr/bin/setfacl
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-87662-3
  - audit_rules_execution_setfacl
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Any Attempts to Run setfacl - Perform remediation of Audit rules for
    /usr/bin/setfacl 32 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/setfacl -F perm=x
        -F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/bin/setfacl
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/setfacl -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/bin/setfacl
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87662-3
  - audit_rules_execution_setfacl
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Any Attempts to Run setfacl - Perform remediation of Audit rules for
    /usr/bin/setfacl 64 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/setfacl -F perm=x
        -F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/bin/setfacl
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/setfacl -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/bin/setfacl
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-87662-3
  - audit_rules_execution_setfacl
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules setfacl  oval:ssg-test_audit_rules_execution_setfacl_augenrules_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_setfacl_augenrules_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/bin\/setfacl(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules setfacl  oval:ssg-test_audit_rules_execution_setfacl_augenrules_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_setfacl_augenrules_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/bin\/setfacl(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl setfacl  oval:ssg-test_audit_rules_execution_setfacl_auditctl_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_setfacl_auditctl_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/bin\/setfacl(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl setfacl  oval:ssg-test_audit_rules_execution_setfacl_auditctl_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_setfacl_auditctl_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/bin\/setfacl(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Any Attempts to Run chconxccdf_org.ssgproject.content_rule_audit_rules_execution_chcon mediumCCE-87762-1

Record Any Attempts to Run chcon

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_execution_chcon
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_execution_chcon:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-87762-1

References:
cis-csc1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2
iso27001-2013A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
cis6.3.3.15
Description
At a minimum, the audit system should collect the execution of privileged commands for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add a line of the following form to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add a line of the following form to /etc/audit/audit.rules, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
Rationale
Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threats.

Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Retrieve hardware architecture of the underlying system
OTHER_FILTERS="-F path=/usr/bin/chcon -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""


[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
  ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
  # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87762-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - audit_rules_execution_chcon
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Any Attempts to Run chcon - Set architecture for audit /usr/bin/chcon
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-87762-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - audit_rules_execution_chcon
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Any Attempts to Run chcon - Perform remediation of Audit rules for
    /usr/bin/chcon 32 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chcon -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/bin/chcon
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chcon -F perm=x -F auid>=1000
        -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/bin/chcon
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87762-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - audit_rules_execution_chcon
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Any Attempts to Run chcon - Perform remediation of Audit rules for
    /usr/bin/chcon 64 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chcon -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/bin/chcon
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chcon -F perm=x -F auid>=1000
        -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/bin/chcon
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-87762-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - audit_rules_execution_chcon
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules chcon  oval:ssg-test_audit_rules_execution_chcon_augenrules_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_chcon_augenrules_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/bin\/chcon(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules chcon  oval:ssg-test_audit_rules_execution_chcon_augenrules_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_chcon_augenrules_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/bin\/chcon(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl chcon  oval:ssg-test_audit_rules_execution_chcon_auditctl_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_chcon_auditctl_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/bin\/chcon(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl chcon  oval:ssg-test_audit_rules_execution_chcon_auditctl_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_execution_chcon_auditctl_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/bin\/chcon(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects File Deletion Events by User - renamexccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_rename mediumCCE-90733-7

Ensure auditd Collects File Deletion Events by User - rename

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_rename
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_file_deletion_events_rename:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90733-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.7
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
anssiR73
cis6.3.3.13
pcidss410.2.1.7, 10.2.1, 10.2
Description
At a minimum, the audit system should collect file deletion events for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S rename -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S rename -F auid>=1000 -F auid!=unset -F key=delete
Rationale
Auditing file deletions will create an audit trail for files that are removed from the system. The audit trail could aid in system troubleshooting, as well as, detecting malicious processes that attempt to delete log files to conceal their presence.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="rename"
	KEY="delete"
	SYSCALL_GROUPING="unlink unlinkat rename renameat renameat2 rmdir"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90733-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_rename
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit rename tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-90733-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_rename
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for rename for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - rename
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of rename in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
    set_fact: audit_file="/etc/audit/rules.d/delete.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - rename
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of rename in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-90733-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_rename
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for rename for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - rename
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of rename in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
    set_fact: audit_file="/etc/audit/rules.d/delete.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - rename
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of rename in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-90733-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_rename
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit rename  oval:ssg-test_32bit_ardm_rename_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_rename_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+rename[\s]+|([\s]+|[,])rename([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit rename  oval:ssg-test_64bit_ardm_rename_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_rename_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+rename[\s]+|([\s]+|[,])rename([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit rename  oval:ssg-test_32bit_ardm_rename_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_rename_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+rename[\s]+|([\s]+|[,])rename([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit rename  oval:ssg-test_64bit_ardm_rename_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_rename_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+rename[\s]+|([\s]+|[,])rename([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects File Deletion Events by User - renameatxccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_renameat mediumCCE-90237-9

Ensure auditd Collects File Deletion Events by User - renameat

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_renameat
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_file_deletion_events_renameat:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90237-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.7
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
anssiR73
cis6.3.3.13
pcidss410.2.1.7, 10.2.1, 10.2
Description
At a minimum, the audit system should collect file deletion events for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S renameat -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S renameat -F auid>=1000 -F auid!=unset -F key=delete
Rationale
Auditing file deletions will create an audit trail for files that are removed from the system. The audit trail could aid in system troubleshooting, as well as, detecting malicious processes that attempt to delete log files to conceal their presence.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="renameat"
	KEY="delete"
	SYSCALL_GROUPING="unlink unlinkat rename renameat renameat2 rmdir"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90237-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_renameat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit renameat tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-90237-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_renameat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for renameat for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - renameat
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of renameat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
    set_fact: audit_file="/etc/audit/rules.d/delete.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - renameat
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of renameat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90237-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_renameat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for renameat for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - renameat
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of renameat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
    set_fact: audit_file="/etc/audit/rules.d/delete.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - renameat
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of renameat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-90237-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_renameat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit renameat  oval:ssg-test_32bit_ardm_renameat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_renameat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+renameat[\s]+|([\s]+|[,])renameat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit renameat  oval:ssg-test_64bit_ardm_renameat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_renameat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+renameat[\s]+|([\s]+|[,])renameat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit renameat  oval:ssg-test_32bit_ardm_renameat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_renameat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+renameat[\s]+|([\s]+|[,])renameat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit renameat  oval:ssg-test_64bit_ardm_renameat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_renameat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+renameat[\s]+|([\s]+|[,])renameat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects File Deletion Events by User - renameat2xccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_renameat2 mediumCCE-86188-0

Ensure auditd Collects File Deletion Events by User - renameat2

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_renameat2
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_file_deletion_events_renameat2:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-86188-0

References:
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
anssiR73
cis6.3.3.13
pcidss410.2.1.7, 10.2.1, 10.2
Description
At a minimum, the audit system should collect file deletion events for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S renameat2 -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S renameat2 -F auid>=1000 -F auid!=unset -F key=delete
Rationale
Auditing file deletions will create an audit trail for files that are removed from the system. The audit trail could aid in system troubleshooting, as well as, detecting malicious processes that attempt to delete log files to conceal their presence.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="renameat2"
	KEY="delete"
	SYSCALL_GROUPING="unlink unlinkat rename renameat renameat2 rmdir"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86188-0
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_renameat2
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit renameat2 tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-86188-0
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_renameat2
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for renameat2 for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - renameat2
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of renameat2 in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
    set_fact: audit_file="/etc/audit/rules.d/delete.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - renameat2
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of renameat2 in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86188-0
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_renameat2
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for renameat2 for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - renameat2
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of renameat2 in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
    set_fact: audit_file="/etc/audit/rules.d/delete.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - renameat2
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of renameat2 in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-86188-0
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_renameat2
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit renameat2  oval:ssg-test_32bit_ardm_renameat2_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_renameat2_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+renameat2[\s]+|([\s]+|[,])renameat2([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit renameat2  oval:ssg-test_64bit_ardm_renameat2_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_renameat2_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+renameat2[\s]+|([\s]+|[,])renameat2([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit renameat2  oval:ssg-test_32bit_ardm_renameat2_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_renameat2_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+renameat2[\s]+|([\s]+|[,])renameat2([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit renameat2  oval:ssg-test_64bit_ardm_renameat2_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_renameat2_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+renameat2[\s]+|([\s]+|[,])renameat2([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects File Deletion Events by User - unlinkatxccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_unlinkat mediumCCE-87813-2

Ensure auditd Collects File Deletion Events by User - unlinkat

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_unlinkat
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_file_deletion_events_unlinkat:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-87813-2

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.7
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
anssiR73
cis6.3.3.13
pcidss410.2.1.7, 10.2.1, 10.2
Description
At a minimum, the audit system should collect file deletion events for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S unlinkat -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S unlinkat -F auid>=1000 -F auid!=unset -F key=delete
Rationale
Auditing file deletions will create an audit trail for files that are removed from the system. The audit trail could aid in system troubleshooting, as well as, detecting malicious processes that attempt to delete log files to conceal their presence.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="unlinkat"
	KEY="delete"
	SYSCALL_GROUPING="unlink unlinkat rename renameat renameat2 rmdir"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87813-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_unlinkat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit unlinkat tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-87813-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_unlinkat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for unlinkat for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - unlinkat
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of unlinkat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
    set_fact: audit_file="/etc/audit/rules.d/delete.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - unlinkat
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of unlinkat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87813-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_unlinkat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for unlinkat for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - unlinkat
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of unlinkat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
    set_fact: audit_file="/etc/audit/rules.d/delete.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - unlinkat
      syscall_grouping:
      - unlink
      - unlinkat
      - rename
      - renameat
      - renameat2
      - rmdir

  - name: Check existence of unlinkat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=delete
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-87813-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_file_deletion_events_unlinkat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit unlinkat  oval:ssg-test_32bit_ardm_unlinkat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_unlinkat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+unlinkat[\s]+|([\s]+|[,])unlinkat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit unlinkat  oval:ssg-test_64bit_ardm_unlinkat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_unlinkat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+unlinkat[\s]+|([\s]+|[,])unlinkat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit unlinkat  oval:ssg-test_32bit_ardm_unlinkat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_unlinkat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+unlinkat[\s]+|([\s]+|[,])unlinkat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit unlinkat  oval:ssg-test_64bit_ardm_unlinkat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_unlinkat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+unlinkat[\s]+|([\s]+|[,])unlinkat([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Unsuccessful Access Attempts to Files - creatxccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_creat mediumCCE-87052-7

Record Unsuccessful Access Attempts to Files - creat

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_creat
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_unsuccessful_file_modification_creat:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-87052-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.4, Req-10.2.1
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205
app-srg-ctrSRG-APP-000495-CTR-001235
anssiR73
cis6.3.3.7
Description
At a minimum, the audit system should collect unauthorized file accesses for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
Rationale
Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing these events could serve as evidence of potential system compromise.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="creat"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EACCES"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EPERM"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87052-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_creat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit creat tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-87052-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_creat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for creat EACCES for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - creat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of creat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - creat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of creat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-87052-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_creat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for creat EACCES for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - creat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of creat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - creat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of creat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-87052-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_creat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for creat EPERM for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - creat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of creat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - creat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of creat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-87052-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_creat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for creat EPERM for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - creat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of creat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - creat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of creat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-87052-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_creat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_creat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_creat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_creat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_creat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^/etc/audit/rules\.d/.*\.rules$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_creat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_creat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_creat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_creat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_creat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_creat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
/etc/audit/audit.rules1

audit auditctl 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_creat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_creat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
/etc/audit/audit.rules1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_creat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_creat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
/etc/audit/audit.rules1

audit auditctl 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_creat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_creat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+creat[\s]+|([\s]+|[,])creat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
/etc/audit/audit.rules1
Record Unsuccessful Access Attempts to Files - ftruncatexccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_ftruncate mediumCCE-86729-1

Record Unsuccessful Access Attempts to Files - ftruncate

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_ftruncate
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_unsuccessful_file_modification_ftruncate:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-86729-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.4, Req-10.2.1
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205
app-srg-ctrSRG-APP-000495-CTR-001235
anssiR73
cis6.3.3.7
Description
At a minimum, the audit system should collect unauthorized file accesses for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
Rationale
Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing these events could serve as evidence of potential system compromise.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="ftruncate"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EACCES"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EPERM"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86729-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_ftruncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit ftruncate tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-86729-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_ftruncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for ftruncate EACCES for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - ftruncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of ftruncate in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - ftruncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of ftruncate in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86729-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_ftruncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for ftruncate EACCES for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - ftruncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of ftruncate in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - ftruncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of ftruncate in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-86729-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_ftruncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for ftruncate EPERM for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - ftruncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of ftruncate in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - ftruncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of ftruncate in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86729-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_ftruncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for ftruncate EPERM for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - ftruncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of ftruncate in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - ftruncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of ftruncate in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-86729-1
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_ftruncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_ftruncate_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_ftruncate_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_ftruncate_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_ftruncate_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_ftruncate_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_ftruncate_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_ftruncate_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_ftruncate_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_ftruncate_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_ftruncate_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

audit auditctl 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_ftruncate_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_ftruncate_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_ftruncate_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_ftruncate_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

audit auditctl 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_ftruncate_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_ftruncate_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+ftruncate[\s]+|([\s]+|[,])ftruncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1
Record Unsuccessful Access Attempts to Files - openxccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_open mediumCCE-87349-7

Record Unsuccessful Access Attempts to Files - open

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_open
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_unsuccessful_file_modification_open:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-87349-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.4, Req-10.2.1
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205
app-srg-ctrSRG-APP-000495-CTR-001235
anssiR73
cis6.3.3.7
Description
At a minimum, the audit system should collect unauthorized file accesses for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
Rationale
Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing these events could serve as evidence of potential system compromise.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="open"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EACCES"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EPERM"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87349-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_open
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit open tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-87349-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_open
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for open EACCES for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - open
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of open in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - open
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of open in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-87349-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_open
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for open EACCES for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - open
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of open in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - open
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of open in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-87349-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_open
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for open EPERM for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - open
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of open in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - open
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of open in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-87349-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_open
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for open EPERM for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - open
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of open in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - open
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of open in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-87349-7
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_open
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_open_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_open_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_open_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_open_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_open_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_open_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_open_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_open_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_open_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_open_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

audit auditctl 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_open_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_open_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_open_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_open_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

audit auditctl 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_open_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_open_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+open[\s]+|([\s]+|[,])open([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1
Record Unsuccessful Access Attempts to Files - openatxccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_openat mediumCCE-89291-9

Record Unsuccessful Access Attempts to Files - openat

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_openat
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_unsuccessful_file_modification_openat:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89291-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.4, Req-10.2.1
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205
app-srg-ctrSRG-APP-000495-CTR-001235
anssiR73
cis6.3.3.7
Description
At a minimum, the audit system should collect unauthorized file accesses for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
Rationale
Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing these events could serve as evidence of potential system compromise.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="openat"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EACCES"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EPERM"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89291-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_openat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit openat tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-89291-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_openat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for openat EACCES for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - openat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of openat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - openat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of openat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89291-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_openat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for openat EACCES for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - openat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of openat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - openat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of openat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-89291-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_openat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for openat EPERM for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - openat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of openat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - openat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of openat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89291-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_openat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for openat EPERM for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - openat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of openat in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - openat
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of openat in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-89291-9
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_openat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_openat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_openat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_openat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_openat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_openat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_openat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_openat_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_openat_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_openat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_openat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

audit auditctl 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_openat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_openat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_openat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_openat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

audit auditctl 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_openat_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_openat_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+openat[\s]+|([\s]+|[,])openat([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1
Record Unsuccessful Access Attempts to Files - truncatexccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_truncate mediumCCE-89869-2

Record Unsuccessful Access Attempts to Files - truncate

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_unsuccessful_file_modification_truncate
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_unsuccessful_file_modification_truncate:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89869-2

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.4, Req-10.2.1
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205
app-srg-ctrSRG-APP-000495-CTR-001235
anssiR73
cis6.3.3.7
Description
At a minimum, the audit system should collect unauthorized file accesses for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
Rationale
Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing these events could serve as evidence of potential system compromise.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="truncate"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EACCES"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F exit=-EPERM"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89869-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_truncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit truncate tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-89869-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_truncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for truncate EACCES for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - truncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of truncate in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - truncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of truncate in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89869-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_truncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for truncate EACCES for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - truncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of truncate in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - truncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of truncate in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-89869-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_truncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for truncate EPERM for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - truncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of truncate in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - truncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of truncate in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89869-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_truncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for truncate EPERM for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - truncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of truncate in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
    set_fact: audit_file="/etc/audit/rules.d/access.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - truncate
      syscall_grouping:
      - creat
      - ftruncate
      - truncate
      - open
      - openat
      - open_by_handle_at

  - name: Check existence of truncate in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
        (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
        -F auid>=1000 -F auid!=unset -F key=access
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-89869-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.1
  - PCI-DSS-Req-10.2.4
  - audit_rules_unsuccessful_file_modification_truncate
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_truncate_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_truncate_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_truncate_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_truncate_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_truncate_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_truncate_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit augenrules 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_truncate_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_truncate_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit file eacces  oval:ssg-test_32bit_arufm_eacces_truncate_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eacces_truncate_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

audit auditctl 32-bit file eperm  oval:ssg-test_32bit_arufm_eperm_truncate_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_arufm_eperm_truncate_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit file eacces  oval:ssg-test_64bit_arufm_eacces_truncate_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eacces_truncate_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EACCES)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1

audit auditctl 64-bit file eperm  oval:ssg-test_64bit_arufm_eperm_truncate_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_arufm_eperm_truncate_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*
^[\s]*-a[\s]+always,exit[\s]+(?:-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+truncate[\s]+|([\s]+|[,])truncate([\s]+|[,])))(?:(?!-F[\s]+a\d&).)*(?:-F\s+exit=-EPERM)[\s]+(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295)[\s]+)(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1
Ensure auditd Collects Information on Kernel Module Unloading - delete_modulexccdf_org.ssgproject.content_rule_audit_rules_kernel_module_loading_delete mediumCCE-89982-3

Ensure auditd Collects Information on Kernel Module Unloading - delete_module

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_kernel_module_loading_delete
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_kernel_module_loading_delete:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89982-3

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.7
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000504-CTR-001280
anssiR73
cis6.3.3.19
Description
To capture kernel module unloading events, use following line, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S delete_module -F auid>=1000 -F auid!=unset -F key=modules
Place to add the line depends on a way auditd daemon is configured. If it is configured to use the augenrules program (the default), add the line to a file with suffix .rules in the directory /etc/audit/rules.d. If the auditd daemon is configured to use the auditctl utility, add the line to file /etc/audit/audit.rules.
Rationale
The removal of kernel modules can be used to alter the behavior of the kernel and potentially introduce malicious code into kernel space. It is important to have an audit trail of modules that have been introduced into the kernel.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
# Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
#       it's required on a 64-bit system to check also for the presence
#       of 32-bit's equivalent of the corresponding rule.
#       (See `man 7 audit.rules` for details )
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	
	SYSCALL="delete_module"
	KEY="modules"
	SYSCALL_GROUPING="delete_module"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89982-3
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_delete
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set architecture for audit delete_module tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-89982-3
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_delete
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Perform remediation of Audit rules for delete_module for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - delete_module
      syscall_grouping: []

  - name: Check existence of delete_module in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
    set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - delete_module
      syscall_grouping: []

  - name: Check existence of delete_module in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89982-3
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_delete
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Perform remediation of Audit rules for delete_module for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - delete_module
      syscall_grouping: []

  - name: Check existence of delete_module in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
    set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - delete_module
      syscall_grouping: []

  - name: Check existence of delete_module in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-89982-3
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_delete
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,-a%20always%2Cexit%20-F%20arch%3Db32%20-S%20delete_module%20-k%20module-change%0A-a%20always%2Cexit%20-F%20arch%3Db64%20-S%20delete_module%20-k%20module-change%0A
        mode: 0600
        path: /etc/audit/rules.d/75-kernel-module-loading-delete.rules
        overwrite: true
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit delete_module  oval:ssg-test_32bit_ardm_delete_module_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_delete_module_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+delete_module[\s]+|([\s]+|[,])delete_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit delete_module  oval:ssg-test_64bit_ardm_delete_module_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_delete_module_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+delete_module[\s]+|([\s]+|[,])delete_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit delete_module  oval:ssg-test_32bit_ardm_delete_module_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_delete_module_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+delete_module[\s]+|([\s]+|[,])delete_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit delete_module  oval:ssg-test_64bit_ardm_delete_module_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_delete_module_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+delete_module[\s]+|([\s]+|[,])delete_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects Information on Kernel Module Loading and Unloading - finit_modulexccdf_org.ssgproject.content_rule_audit_rules_kernel_module_loading_finit mediumCCE-88638-2

Ensure auditd Collects Information on Kernel Module Loading and Unloading - finit_module

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_kernel_module_loading_finit
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_kernel_module_loading_finit:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88638-2

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.7
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000504-CTR-001280
anssiR73
cis6.3.3.19
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d to capture kernel module loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:
-a always,exit -F arch=ARCH -S finit_module -F auid>=1000 -F auid!=unset -F key=modules
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules file in order to capture kernel module loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:
-a always,exit -F arch=ARCH -S finit_module -F auid>=1000 -F auid!=unset -F key=modules
Rationale
The addition/removal of kernel modules can be used to alter the behavior of the kernel and potentially introduce malicious code into kernel space. It is important to have an audit trail of modules that have been introduced into the kernel.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
# Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
#       it's required on a 64-bit system to check also for the presence
#       of 32-bit's equivalent of the corresponding rule.
#       (See `man 7 audit.rules` for details )
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	
	SYSCALL="finit_module"
	KEY="modules"
	SYSCALL_GROUPING="init_module finit_module"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88638-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_finit
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set architecture for audit finit_module tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-88638-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_finit
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Perform remediation of Audit rules for finit_module for x86 platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - finit_module
      syscall_grouping:
      - init_module
      - finit_module

  - name: Check existence of finit_module in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
    set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - finit_module
      syscall_grouping:
      - init_module
      - finit_module

  - name: Check existence of finit_module in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88638-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_finit
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Perform remediation of Audit rules for finit_module for x86_64 platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - finit_module
      syscall_grouping:
      - init_module
      - finit_module

  - name: Check existence of finit_module in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
    set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - finit_module
      syscall_grouping:
      - init_module
      - finit_module

  - name: Check existence of finit_module in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-88638-2
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_finit
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,-a%20always%2Cexit%20-F%20arch%3Db32%20-S%20finit_module%20-k%20module-change%0A-a%20always%2Cexit%20-F%20arch%3Db64%20-S%20finit_module%20-k%20module-change%0A
        mode: 0600
        path: /etc/audit/rules.d/75-kernel-module-loading-finit.rules
        overwrite: true
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit finit_module  oval:ssg-test_32bit_ardm_finit_module_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_finit_module_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+finit_module[\s]+|([\s]+|[,])finit_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit finit_module  oval:ssg-test_64bit_ardm_finit_module_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_finit_module_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+finit_module[\s]+|([\s]+|[,])finit_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit finit_module  oval:ssg-test_32bit_ardm_finit_module_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_finit_module_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+finit_module[\s]+|([\s]+|[,])finit_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit finit_module  oval:ssg-test_64bit_ardm_finit_module_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_finit_module_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+finit_module[\s]+|([\s]+|[,])finit_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects Information on Kernel Module Loading - init_modulexccdf_org.ssgproject.content_rule_audit_rules_kernel_module_loading_init mediumCCE-90172-8

Ensure auditd Collects Information on Kernel Module Loading - init_module

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_kernel_module_loading_init
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_kernel_module_loading_init:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90172-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.7
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000504-CTR-001280
anssiR73
cis6.3.3.19
Description
To capture kernel module loading events, use following line, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S init_module -F auid>=1000 -F auid!=unset -F key=modules
Place to add the line depends on a way auditd daemon is configured. If it is configured to use the augenrules program (the default), add the line to a file with suffix .rules in the directory /etc/audit/rules.d. If the auditd daemon is configured to use the auditctl utility, add the line to file /etc/audit/audit.rules.
Rationale
The addition of kernel modules can be used to alter the behavior of the kernel and potentially introduce malicious code into kernel space. It is important to have an audit trail of modules that have been introduced into the kernel.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
# Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
#       it's required on a 64-bit system to check also for the presence
#       of 32-bit's equivalent of the corresponding rule.
#       (See `man 7 audit.rules` for details )
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	
	SYSCALL="init_module"
	KEY="modules"
	SYSCALL_GROUPING="init_module finit_module"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90172-8
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_init
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set architecture for audit init_module tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-90172-8
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_init
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Perform remediation of Audit rules for init_module for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - init_module
      syscall_grouping:
      - init_module
      - finit_module

  - name: Check existence of init_module in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
    set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - init_module
      syscall_grouping:
      - init_module
      - finit_module

  - name: Check existence of init_module in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90172-8
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_init
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Perform remediation of Audit rules for init_module for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - init_module
      syscall_grouping:
      - init_module
      - finit_module

  - name: Check existence of init_module in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
    set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - init_module
      syscall_grouping:
      - init_module
      - finit_module

  - name: Check existence of init_module in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-90172-8
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - audit_rules_kernel_module_loading_init
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,-a%20always%2Cexit%20-F%20arch%3Db32%20-S%20init_module%20-k%20module-change%0A-a%20always%2Cexit%20-F%20arch%3Db64%20-S%20init_module%20-k%20module-change%0A
        mode: 0600
        path: /etc/audit/rules.d/75-kernel-module-loading-init.rules
        overwrite: true
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit init_module  oval:ssg-test_32bit_ardm_init_module_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_init_module_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+init_module[\s]+|([\s]+|[,])init_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit init_module  oval:ssg-test_64bit_ardm_init_module_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_init_module_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+init_module[\s]+|([\s]+|[,])init_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit init_module  oval:ssg-test_32bit_ardm_init_module_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_init_module_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+init_module[\s]+|([\s]+|[,])init_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit init_module  oval:ssg-test_64bit_ardm_init_module_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_init_module_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+init_module[\s]+|([\s]+|[,])init_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects Information on Kernel Module Loading and Unloading - query_modulexccdf_org.ssgproject.content_rule_audit_rules_kernel_module_loading_query mediumCCE-90015-9

Ensure auditd Collects Information on Kernel Module Loading and Unloading - query_module

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_kernel_module_loading_query
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_kernel_module_loading_query:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90015-9

References:
cis6.3.3.19
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d to capture kernel module loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:
-a always,exit -F arch=ARCH -S query_module -F auid>=1000 -F auid!=unset -F key=modules
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules file in order to capture kernel module loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:
-a always,exit -F arch=ARCH -S query_module -F auid>=1000 -F auid!=unset -F key=modules
Rationale
The addition/removal of kernel modules can be used to alter the behavior of the kernel and potentially introduce malicious code into kernel space. It is important to have an audit trail of modules that have been introduced into the kernel.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
# Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
#       it's required on a 64-bit system to check also for the presence
#       of 32-bit's equivalent of the corresponding rule.
#       (See `man 7 audit.rules` for details )
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="query_module"
	KEY="modules"
	SYSCALL_GROUPING="init_module query_module"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90015-9
  - audit_rules_kernel_module_loading_query
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set architecture for audit query_module tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-90015-9
  - audit_rules_kernel_module_loading_query
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Perform remediation of Audit rules for query_module for x86 platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - query_module
      syscall_grouping:
      - init_module
      - query_module

  - name: Check existence of query_module in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
    set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - query_module
      syscall_grouping:
      - init_module
      - query_module

  - name: Check existence of query_module in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-90015-9
  - audit_rules_kernel_module_loading_query
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Perform remediation of Audit rules for query_module for x86_64 platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - query_module
      syscall_grouping:
      - init_module
      - query_module

  - name: Check existence of query_module in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
    set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - query_module
      syscall_grouping:
      - init_module
      - query_module

  - name: Check existence of query_module in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=module-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-90015-9
  - audit_rules_kernel_module_loading_query
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit query_module  oval:ssg-test_32bit_ardm_query_module_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_query_module_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+query_module[\s]+|([\s]+|[,])query_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit query_module  oval:ssg-test_64bit_ardm_query_module_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_query_module_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+query_module[\s]+|([\s]+|[,])query_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit query_module  oval:ssg-test_32bit_ardm_query_module_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_query_module_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+query_module[\s]+|([\s]+|[,])query_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit query_module  oval:ssg-test_64bit_ardm_query_module_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_query_module_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+query_module[\s]+|([\s]+|[,])query_module([\s]+|[,]))).*(?:-F\s+auid>=1000[\s]+)(?:-F\s+auid!=(unset|4294967295))\s+(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects Information on the Use of Privileged Commandsxccdf_org.ssgproject.content_rule_audit_rules_privileged_commands mediumCCE-88170-6

Ensure auditd Collects Information on the Use of Privileged Commands

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_privileged_commands
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_privileged_commands:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-88170-6

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO08.04, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.05, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.5, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.3.4.5.9, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 3.9, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
ism0582, 0584, 05885, 0586, 0846, 0957
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.1, A.16.1.2, A.16.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.3, A.6.2.1, A.6.2.2
nerc-cipCIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nistAC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-2, DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, DE.DP-4, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, RS.CO-2
pcidssReq-10.2.2
os-srgSRG-OS-000327-GPOS-00127
anssiR73
cis6.3.3.6
Description
The audit system should collect information about usage of privileged commands for all users. These are commands with suid or sgid bits on and they are specially risky in local block device partitions not mounted with noexec and nosuid options. Therefore, these partitions should be first identified by the following command:
findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,) | grep -Pv "noexec|nosuid"
For all partitions listed by the previous command, it is necessary to search for setuid / setgid programs using the following command:
$ sudo find PARTITION -xdev -perm /6000 -type f 2>/dev/null
For each setuid / setgid program identified by the previous command, an audit rule must be present in the appropriate place using the following line structure, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=PROG_PATH -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup, add the line to a file with suffix .rules in the /etc/audit/rules.d directory, replacing the PROG_PATH part with the full path of that setuid / setgid identified program. If the auditd daemon is configured to use the auditctl utility instead, add the line to the /etc/audit/audit.rules file, also replacing the PROG_PATH part with the full path of that setuid / setgid identified program.
Rationale
Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern that can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threats.

Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.
Warnings
warning  This rule checks for multiple syscalls related to privileged commands. If needed to check specific privileged commands, other more specific rules should be considered. For example:
  • audit_rules_privileged_commands_su
  • audit_rules_privileged_commands_umount
  • audit_rules_privileged_commands_passwd
warning  Note that OVAL check and Bash / Ansible remediation of this rule explicitly excludes file systems mounted at /proc directory and its subdirectories. It is a virtual file system and it doesn't contain executable applications. At the same time, interacting with this file system during check or remediation caused undesirable errors.

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

ACTION_ARCH_FILTERS="-a always,exit"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""

function add_audit_rule()
{
    local PRIV_CMD="$1"
    local OTHER_FILTERS="-F path=$PRIV_CMD -F perm=x"
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0600 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod 0600 ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()


    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod 0600 ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
}

if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
  PRIV_CMDS=$(find / -perm /6000 -type f -not -path "/sysroot/*" 2>/dev/null)
  for PRIV_CMD in $PRIV_CMDS; do
    add_audit_rule $PRIV_CMD
  done
else
  FILTER_NODEV=$(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)
  PARTITIONS=$(findmnt -n -l -k -it "$FILTER_NODEV" | grep -Pv "noexec|nosuid|/proc($|/.*$)" | awk '{ print $1 }')
  for PARTITION in $PARTITIONS; do
    PRIV_CMDS=$(find "${PARTITION}" -xdev -perm /6000 -type f 2>/dev/null)
    for PRIV_CMD in $PRIV_CMDS; do
      add_audit_rule $PRIV_CMD
    done
  done
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88170-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - audit_rules_privileged_commands
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure auditd Collects Information on the Use of Privileged Commands - Set
    List of Mount Points Which Permits Execution of Privileged Commands
  ansible.builtin.set_fact:
    privileged_mount_points: '{{ (ansible_facts.mounts | rejectattr(''options'', ''search'',
      ''noexec|nosuid'') | rejectattr(''mount'', ''match'', ''/proc($|/.*$)'') | map(attribute=''mount'')
      | list ) }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88170-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - audit_rules_privileged_commands
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure auditd Collects Information on the Use of Privileged Commands - Search
    for Privileged Commands in Eligible Mount Points
  ansible.builtin.shell:
    cmd: find {{ item }} -xdev -perm /6000 -type f 2>/dev/null
  register: result_privileged_commands_search
  changed_when: false
  failed_when: false
  with_items: '{{ privileged_mount_points }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88170-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - audit_rules_privileged_commands
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure auditd Collects Information on the Use of Privileged Commands - Set
    List of Privileged Commands Found in Eligible Mount Points
  ansible.builtin.set_fact:
    privileged_commands: '{{ privileged_commands | default([]) + item.stdout_lines
      }}'
  loop: '{{ result_privileged_commands_search.results }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - item is not skipped
  tags:
  - CCE-88170-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - audit_rules_privileged_commands
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure auditd Collects Information on the Use of Privileged Commands - Privileged
    Commands are Present in the System
  block:

  - name: Ensure auditd Collects Information on the Use of Privileged Commands - Ensure
      Rules for All Privileged Commands in augenrules Format
    ansible.builtin.lineinfile:
      path: /etc/audit/rules.d/privileged.rules
      line: -a always,exit -F path={{ item }} -F perm=x -F auid>=1000 -F auid!=unset
        -F key=privileged
      regexp: ^.*path={{ item | regex_escape() }} .*$
      create: true
    with_items:
    - '{{ privileged_commands }}'

  - name: Ensure auditd Collects Information on the Use of Privileged Commands - Ensure
      Rules for All Privileged Commands in auditctl Format
    ansible.builtin.lineinfile:
      path: /etc/audit/audit.rules
      line: -a always,exit -F path={{ item }} -F perm=x -F auid>=1000 -F auid!=unset
        -F key=privileged
      regexp: ^.*path={{ item | regex_escape() }} .*$
      create: true
    with_items:
    - '{{ privileged_commands }}'

  - name: Ensure auditd Collects Information on the Use of Privileged Commands - Search
      for Duplicated Rules in Other Files
    ansible.builtin.find:
      paths: /etc/audit/rules.d
      recurse: false
      contains: ^-a always,exit -F path={{ item }} .*$
      patterns: '*.rules'
    with_items:
    - '{{ privileged_commands }}'
    register: result_augenrules_files

  - name: Ensure auditd Collects Information on the Use of Privileged Commands - Ensure
      Rules for Privileged Commands are Defined Only in One File
    ansible.builtin.lineinfile:
      path: '{{ item.1.path }}'
      regexp: ^-a always,exit -F path={{ item.0.item }} .*$
      state: absent
    with_subelements:
    - '{{ result_augenrules_files.results }}'
    - files
    when:
    - item.1.path != '/etc/audit/rules.d/privileged.rules'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - privileged_commands is defined
  tags:
  - CCE-88170-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - audit_rules_privileged_commands
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

There is one augenrules rule for each privileged command on the system.  oval:ssg-test_augenrules_all_priv_cmds_covered_bootc:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_priv_cmds_from_augenrules_bootc:obj:1 of type textfilecontent54_object
FilepathPatternInstanceFilter
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/bin/sudo
/usr/bin/chage
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/su
/usr/bin/chfn
/usr/bin/mount
/usr/bin/passwd
/usr/bin/umount
/usr/sbin/grub2-set-bootflag
/usr/bin/chsh
/usr/bin/write
/usr/bin/pkexec
/usr/sbin/unix_chkpwd
/usr/sbin/pam_timestamp_check
/usr/bin/crontab
/usr/libexec/utempter/utempter
^[\s]*-a always,exit (?:-F path=([\S]+))+(?: -F perm=x)? -F auid>=1000 -F auid!=(?:4294967295|unset)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1oval:ssg-state_unprivileged_commands_bootc:ste:1

Count of augenrules for priv cmds matches the count of priv cmds in the system  oval:ssg-test_augenrules_count_matches_system_priv_cmds_bootc:tst:1  error

Following items have been found on the system:
Result of item-state comparisonVar refValue
erroroval:ssg-var_audit_rules_privileged_commands_priv_cmds_count_bootc:var:118

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

There is one augenrules rule for each privileged command on the system.  oval:ssg-test_augenrules_all_priv_cmds_covered:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_priv_cmds_from_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstanceFilter
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/bin/sudo
/usr/bin/chage
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/su
/usr/bin/chfn
/usr/bin/mount
/usr/bin/passwd
/usr/bin/umount
/usr/sbin/grub2-set-bootflag
/usr/bin/chsh
/usr/bin/write
/usr/bin/pkexec
/usr/sbin/unix_chkpwd
/usr/sbin/pam_timestamp_check
/usr/bin/crontab
/usr/libexec/utempter/utempter
/boot/efi
/
^[\s]*-a always,exit (?:-F path=([\S]+))+(?: -F perm=x)? -F auid>=1000 -F auid!=(?:4294967295|unset)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
^/etc/audit/rules\.d/.*\.rules$1oval:ssg-state_unprivileged_commands:ste:1

Count of augenrules for priv cmds matches the count of priv cmds in the system  oval:ssg-test_augenrules_count_matches_system_priv_cmds:tst:1  error

Following items have been found on the system:
Result of item-state comparisonVar refValue
erroroval:ssg-var_audit_rules_privileged_commands_priv_cmds_count:var:118

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

There is one auditctl rule for each privileged command on the system.  oval:ssg-test_auditctl_all_priv_cmds_covered:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_priv_cmds_from_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstanceFilter
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/bin/sudo
/usr/bin/chage
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/su
/usr/bin/chfn
/usr/bin/mount
/usr/bin/passwd
/usr/bin/umount
/usr/sbin/grub2-set-bootflag
/usr/bin/chsh
/usr/bin/write
/usr/bin/pkexec
/usr/sbin/unix_chkpwd
/usr/sbin/pam_timestamp_check
/usr/bin/crontab
/usr/libexec/utempter/utempter
/boot/efi
/
^[\s]*-a always,exit (?:-F path=([\S]+))+(?: -F perm=x)? -F auid>=1000 -F auid!=(?:4294967295|unset)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1oval:ssg-state_unprivileged_commands:ste:1

Count of auditctl rules for priv cmds matches the count of priv cmds in the system  oval:ssg-test_auditctl_count_matches_system_priv_cmds:tst:1  error

Following items have been found on the system:
Result of item-state comparisonVar refValue
erroroval:ssg-var_audit_rules_privileged_commands_priv_cmds_count:var:118

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

There is one auditctl rule for each privileged command on the system.  oval:ssg-test_auditctl_all_priv_cmds_covered:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_priv_cmds_from_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstanceFilter
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/bin/sudo
/usr/bin/chage
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/su
/usr/bin/chfn
/usr/bin/mount
/usr/bin/passwd
/usr/bin/umount
/usr/sbin/grub2-set-bootflag
/usr/bin/chsh
/usr/bin/write
/usr/bin/pkexec
/usr/sbin/unix_chkpwd
/usr/sbin/pam_timestamp_check
/usr/bin/crontab
/usr/libexec/utempter/utempter
/boot/efi
/
^[\s]*-a always,exit (?:-F path=([\S]+))+(?: -F perm=x)? -F auid>=1000 -F auid!=(?:4294967295|unset)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
/etc/audit/audit.rules1oval:ssg-state_unprivileged_commands:ste:1

Count of auditctl rules for priv cmds matches the count of priv cmds in the system  oval:ssg-test_auditctl_count_matches_system_priv_cmds:tst:1  error

Following items have been found on the system:
Result of item-state comparisonVar refValue
erroroval:ssg-var_audit_rules_privileged_commands_priv_cmds_count:var:118
Ensure auditd Collects Information on the Use of Privileged Commands - kmodxccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_kmod mediumCCE-86727-5

Ensure auditd Collects Information on the Use of Privileged Commands - kmod

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_kmod
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_privileged_commands_kmod:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-86727-5

References:
nistAU-3, AU-3.1, AU-12(a), AU-12.1(ii), AU-12.1(iv)AU-12(c), MA-4(1)(a)
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000504-CTR-001280
anssiR73
cis6.3.3.19
Description
At a minimum, the audit system should collect the execution of privileged commands for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add a line of the following form to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add a line of the following form to /etc/audit/audit.rules, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
Rationale
Without generating audit records that are specific to the security and mission needs of the organization, it would be difficult to establish, correlate, and investigate the events relating to an incident or identify those responsible for one. Audit records can be generated from various components within the information system (e.g., module or policy filter).

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Retrieve hardware architecture of the underlying system
OTHER_FILTERS="-F path=/usr/bin/kmod -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""


[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
  ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
  # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86727-5
  - NIST-800-53-AU-12(a)
  - NIST-800-53-AU-12.1(ii)
  - NIST-800-53-AU-12.1(iv)AU-12(c)
  - NIST-800-53-AU-3
  - NIST-800-53-AU-3.1
  - NIST-800-53-MA-4(1)(a)
  - audit_rules_privileged_commands_kmod
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects Information on the Use of Privileged Commands - kmod
    - Set architecture for audit /usr/bin/kmod
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-86727-5
  - NIST-800-53-AU-12(a)
  - NIST-800-53-AU-12.1(ii)
  - NIST-800-53-AU-12.1(iv)AU-12(c)
  - NIST-800-53-AU-3
  - NIST-800-53-AU-3.1
  - NIST-800-53-MA-4(1)(a)
  - audit_rules_privileged_commands_kmod
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects Information on the Use of Privileged Commands - kmod
    - Perform remediation of Audit rules for /usr/bin/kmod 32 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/kmod -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/bin/kmod
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/kmod -F perm=x -F auid>=1000
        -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/bin/kmod
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86727-5
  - NIST-800-53-AU-12(a)
  - NIST-800-53-AU-12.1(ii)
  - NIST-800-53-AU-12.1(iv)AU-12(c)
  - NIST-800-53-AU-3
  - NIST-800-53-AU-3.1
  - NIST-800-53-MA-4(1)(a)
  - audit_rules_privileged_commands_kmod
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects Information on the Use of Privileged Commands - kmod
    - Perform remediation of Audit rules for /usr/bin/kmod 64 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/kmod -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/bin/kmod
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/kmod -F perm=x -F auid>=1000
        -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/bin/kmod
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-86727-5
  - NIST-800-53-AU-12(a)
  - NIST-800-53-AU-12.1(ii)
  - NIST-800-53-AU-12.1(iv)AU-12(c)
  - NIST-800-53-AU-3
  - NIST-800-53-AU-3.1
  - NIST-800-53-MA-4(1)(a)
  - audit_rules_privileged_commands_kmod
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules kmod  oval:ssg-test_audit_rules_privileged_commands_kmod_augenrules_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_privileged_commands_kmod_augenrules_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/bin\/kmod(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules kmod  oval:ssg-test_audit_rules_privileged_commands_kmod_augenrules_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_privileged_commands_kmod_augenrules_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/bin\/kmod(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl kmod  oval:ssg-test_audit_rules_privileged_commands_kmod_auditctl_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_privileged_commands_kmod_auditctl_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/bin\/kmod(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl kmod  oval:ssg-test_audit_rules_privileged_commands_kmod_auditctl_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_privileged_commands_kmod_auditctl_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/bin\/kmod(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects Information on the Use of Privileged Commands - usermodxccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_usermod mediumCCE-87659-9

Ensure auditd Collects Information on the Use of Privileged Commands - usermod

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_privileged_commands_usermod
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_privileged_commands_usermod:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-87659-9

References:
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
cis6.3.3.18
Description
At a minimum, the audit system should collect the execution of privileged commands for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add a line of the following form to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add a line of the following form to /etc/audit/audit.rules, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
Rationale
Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threats.

Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Retrieve hardware architecture of the underlying system
OTHER_FILTERS="-F path=/usr/sbin/usermod -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""


[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
  ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
  # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
  unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87659-9
  - audit_rules_privileged_commands_usermod
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects Information on the Use of Privileged Commands - usermod
    - Set architecture for audit /usr/sbin/usermod
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-87659-9
  - audit_rules_privileged_commands_usermod
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects Information on the Use of Privileged Commands - usermod
    - Perform remediation of Audit rules for /usr/sbin/usermod 32 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset
        (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/usermod -F perm=x
        -F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/sbin/usermod
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset
        (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/usermod -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32{{ syscalls | join(',') }} -F path=/usr/sbin/usermod
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87659-9
  - audit_rules_privileged_commands_usermod
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects Information on the Use of Privileged Commands - usermod
    - Perform remediation of Audit rules for /usr/sbin/usermod 64 bit
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset
        (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
    set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/usermod -F perm=x
        -F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/sbin/usermod
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls: []
      syscall_grouping: []

  - name: Check existence of  in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset
        (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/usermod -F perm=x -F
        auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64{{ syscalls | join(',') }} -F path=/usr/sbin/usermod
        -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-87659-9
  - audit_rules_privileged_commands_usermod
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules usermod  oval:ssg-test_audit_rules_privileged_commands_usermod_augenrules_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_privileged_commands_usermod_augenrules_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/sbin\/usermod(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules usermod  oval:ssg-test_audit_rules_privileged_commands_usermod_augenrules_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_privileged_commands_usermod_augenrules_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/sbin\/usermod(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl usermod  oval:ssg-test_audit_rules_privileged_commands_usermod_auditctl_32bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_privileged_commands_usermod_auditctl_32bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-F[\s]+path=\/usr\/sbin\/usermod(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl usermod  oval:ssg-test_audit_rules_privileged_commands_usermod_auditctl_64bit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_privileged_commands_usermod_auditctl_64bit:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-F[\s]+path=\/usr\/sbin\/usermod(?:[\s]+-F[\s]+perm=x)[\s]+-F[\s]+auid>=1000[\s]+-F[\s]+auid!=(?:4294967295|unset|-1)[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record attempts to alter time through adjtimexxccdf_org.ssgproject.content_rule_audit_rules_time_adjtimex mediumCCE-87633-4

Record attempts to alter time through adjtimex

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_time_adjtimex
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_time_adjtimex:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-87633-4

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.4.2.b
anssiR73
cis6.3.3.4
pcidss410.6.3, 10.6
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S adjtimex -F key=audit_time_rules
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S adjtimex -F key=audit_time_rules
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S adjtimex -F key=audit_time_rules
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S adjtimex -F key=audit_time_rules
The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:
-a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules
Rationale
Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
    # Create expected audit group and audit rule form for particular system call & architecture
    if [ ${ARCH} = "b32" ]
    then
        ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
        # stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
        # so append it to the list of time group system calls to be audited
        SYSCALL="adjtimex settimeofday stime"
        SYSCALL_GROUPING="adjtimex settimeofday stime"
    elif [ ${ARCH} = "b64" ]
    then
        ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
        # stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
        # therefore don't add it to the list of time group system calls to be audited
        SYSCALL="adjtimex settimeofday"
        SYSCALL_GROUPING="adjtimex settimeofday"
    fi
    OTHER_FILTERS=""
    AUID_FILTERS=""
    KEY="audit_time_rules"
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0600 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod 0600 ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()


    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod 0600 ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87633-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_adjtimex
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set architecture for audit tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-87633-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_adjtimex
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Perform remediation of Audit rules for adjtimex for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - adjtimex
      syscall_grouping:
      - adjtimex
      - settimeofday
      - stime

  - name: Check existence of adjtimex in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/audit_time_rules.rules
    set_fact: audit_file="/etc/audit/rules.d/audit_time_rules.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - adjtimex
      syscall_grouping:
      - adjtimex
      - settimeofday
      - stime

  - name: Check existence of adjtimex in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87633-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_adjtimex
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Perform remediation of Audit rules for adjtimex for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - adjtimex
      syscall_grouping:
      - adjtimex
      - settimeofday

  - name: Check existence of adjtimex in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/audit_time_rules.rules
    set_fact: audit_file="/etc/audit/rules.d/audit_time_rules.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_time_rules
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - adjtimex
      syscall_grouping:
      - adjtimex
      - settimeofday
      - stime

  - name: Check existence of adjtimex in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_time_rules
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-87633-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_adjtimex
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,{{ -a%20always%2Cexit%20-F%20arch%3Db64%20-S%20adjtimex%20-k%20audit_time_rules%0A-a%20always%2Cexit%20-F%20arch%3Db32%20-S%20adjtimex%20-k%20audit_time_rules%0A }}
        mode: 0600
        path: /etc/audit/rules.d/75-syscall-adjtimex.rules
        overwrite: true
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit adjtimex  oval:ssg-test_32bit_art_adjtimex_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_art_adjtimex_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32.*(-S[\s]+adjtimex[\s]+|([\s]+|[,])adjtimex([\s]+|[,])).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit adjtimex  oval:ssg-test_64bit_art_adjtimex_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_art_adjtimex_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64.*(-S[\s]+adjtimex[\s]+|([\s]+|[,])adjtimex([\s]+|[,])).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit adjtimex  oval:ssg-test_32bit_art_adjtimex_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_art_adjtimex_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32.*(-S[\s]+adjtimex[\s]+|([\s]+|[,])adjtimex([\s]+|[,])).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit adjtimex  oval:ssg-test_64bit_art_adjtimex_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_art_adjtimex_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64.*(-S[\s]+adjtimex[\s]+|([\s]+|[,])adjtimex([\s]+|[,])).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Attempts to Alter Time Through clock_settimexccdf_org.ssgproject.content_rule_audit_rules_time_clock_settime mediumCCE-88533-5

Record Attempts to Alter Time Through clock_settime

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_time_clock_settime
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_time_clock_settime:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-88533-5

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.4.2.b
anssiR73
cis6.3.3.4
pcidss410.6.3, 10.6
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change
The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:
-a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules
Rationale
Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-F a0=0x0"
	AUID_FILTERS=""
	SYSCALL="clock_settime"
	KEY="time-change"
	SYSCALL_GROUPING=""
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88533-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_clock_settime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set architecture for audit tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-88533-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_clock_settime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Perform remediation of Audit rules for clock_settime for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - clock_settime
      syscall_grouping: []

  - name: Check existence of clock_settime in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F a0=0x0 (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/time-change.rules
    set_fact: audit_file="/etc/audit/rules.d/time-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F a0=0x0 (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F a0=0x0 -F
        key=time-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - clock_settime
      syscall_grouping: []

  - name: Check existence of clock_settime in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F a0=0x0 (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F a0=0x0 (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F a0=0x0 -F
        key=time-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88533-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_clock_settime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Perform remediation of Audit rules for clock_settime for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - clock_settime
      syscall_grouping: []

  - name: Check existence of clock_settime in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F a0=0x0 (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/time-change.rules
    set_fact: audit_file="/etc/audit/rules.d/time-change.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F a0=0x0 (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F a0=0x0 -F
        key=time-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - clock_settime
      syscall_grouping: []

  - name: Check existence of clock_settime in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F a0=0x0 (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F a0=0x0 (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F a0=0x0 -F
        key=time-change
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-88533-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_clock_settime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,{{ -a%20always%2Cexit%20-F%20arch%3Db64%20-S%20clock_settime%20-F%20a0%3D0x0%20-k%20time-change%0A-a%20always%2Cexit%20-F%20arch%3Db32%20-S%20clock_settime%20-F%20a0%3D0x0%20-k%20time-change%0A }}
        mode: 0600
        path: /etc/audit/rules.d/75-syscall-clock-settime.rules
        overwrite: true
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit clock_settime  oval:ssg-test_32bit_art_clock_settime_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_art_clock_settime_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+(-S[\s]+clock_settime[\s]+|([\s]+|[,])clock_settime([\s]+|[,]))-F[\s]+a0=(?:0x)?0[\s]+(?:-F[\s]+key=|-k[\s]+)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit clock_settime  oval:ssg-test_64bit_art_clock_settime_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_art_clock_settime_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+(-S[\s]+clock_settime[\s]+|([\s]+|[,])clock_settime([\s]+|[,]))-F[\s]+a0=(?:0x)?0[\s]+(?:-F[\s]+key=|-k[\s]+)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit clock_settime  oval:ssg-test_32bit_art_clock_settime_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_art_clock_settime_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+(-S[\s]+clock_settime[\s]+|([\s]+|[,])clock_settime([\s]+|[,]))-F[\s]+a0=(?:0x)?0[\s]+(?:-F[\s]+key=|-k[\s]+)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit clock_settime  oval:ssg-test_64bit_art_clock_settime_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_art_clock_settime_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+(-S[\s]+clock_settime[\s]+|([\s]+|[,])clock_settime([\s]+|[,]))-F[\s]+a0=(?:0x)?0[\s]+(?:-F[\s]+key=|-k[\s]+)[\S]+[\s]*$1
Record attempts to alter time through settimeofdayxccdf_org.ssgproject.content_rule_audit_rules_time_settimeofday mediumCCE-90548-9

Record attempts to alter time through settimeofday

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_time_settimeofday
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_time_settimeofday:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-90548-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.4.2.b
cis6.3.3.4
pcidss410.6.3, 10.6
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -S settimeofday -F key=audit_time_rules
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S settimeofday -F key=audit_time_rules
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S settimeofday -F key=audit_time_rules
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S settimeofday -F key=audit_time_rules
The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:
-a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules
Rationale
Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
    # Create expected audit group and audit rule form for particular system call & architecture
    if [ ${ARCH} = "b32" ]
    then
        ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
        # stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
        # so append it to the list of time group system calls to be audited
        SYSCALL="adjtimex settimeofday stime"
        SYSCALL_GROUPING="adjtimex settimeofday stime"
    elif [ ${ARCH} = "b64" ]
    then
        ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
        # stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
        # therefore don't add it to the list of time group system calls to be audited
        SYSCALL="adjtimex settimeofday"
        SYSCALL_GROUPING="adjtimex settimeofday"
    fi
    OTHER_FILTERS=""
    AUID_FILTERS=""
    KEY="audit_time_rules"
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0600 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod 0600 ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()


    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod 0600 ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90548-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_settimeofday
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set architecture for audit tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-90548-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_settimeofday
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Perform remediation of Audit rules for settimeofday for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - settimeofday
      syscall_grouping:
      - adjtimex
      - settimeofday
      - stime

  - name: Check existence of settimeofday in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/audit_time_rules.rules
    set_fact: audit_file="/etc/audit/rules.d/audit_time_rules.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - settimeofday
      syscall_grouping:
      - adjtimex
      - settimeofday
      - stime

  - name: Check existence of settimeofday in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90548-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_settimeofday
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Perform remediation of Audit rules for settimeofday for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - settimeofday
      syscall_grouping:
      - adjtimex
      - settimeofday
      - stime

  - name: Check existence of settimeofday in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/audit_time_rules.rules
    set_fact: audit_file="/etc/audit/rules.d/audit_time_rules.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_time_rules
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - settimeofday
      syscall_grouping:
      - adjtimex
      - settimeofday
      - stime

  - name: Check existence of settimeofday in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_time_rules
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-90548-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_settimeofday
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,{{ -a%20always%2Cexit%20-F%20arch%3Db64%20-S%20settimeofday%20-k%20audit_time_rules%0A-a%20always%2Cexit%20-F%20arch%3Db32%20-S%20settimeofday%20-k%20audit_time_rules%0A }}
        mode: 0600
        path: /etc/audit/rules.d/75-syscall-settimeofday.rules
        overwrite: true
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit settimeofday  oval:ssg-test_32bit_art_settimeofday_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_art_settimeofday_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32.*(-S[\s]+settimeofday[\s]+|([\s]+|[,])settimeofday([\s]+|[,])).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit settimeofday  oval:ssg-test_64bit_art_settimeofday_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_art_settimeofday_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64.*(-S[\s]+settimeofday[\s]+|([\s]+|[,])settimeofday([\s]+|[,])).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit settimeofday  oval:ssg-test_32bit_art_settimeofday_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_art_settimeofday_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32.*(-S[\s]+settimeofday[\s]+|([\s]+|[,])settimeofday([\s]+|[,])).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit settimeofday  oval:ssg-test_64bit_art_settimeofday_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_art_settimeofday_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64.*(-S[\s]+settimeofday[\s]+|([\s]+|[,])settimeofday([\s]+|[,])).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Attempts to Alter the localtime Filexccdf_org.ssgproject.content_rule_audit_rules_time_watch_localtime mediumCCE-89928-6

Record Attempts to Alter the localtime File

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_time_watch_localtime
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_time_watch_localtime:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-89928-6

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.4.2.b
anssiR73
cis6.3.3.4
pcidss410.6.3, 10.6
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules
-a always,exit -F arch=b64 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules
-a always,exit -F arch=b64 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules
Rationale
Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/localtime" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/localtime$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/localtime$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/localtime" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/localtime$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/localtime$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_time_rules.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/localtime" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_time_rules.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_time_rules.rules"
    # If the audit_time_rules.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/localtime" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/localtime$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/localtime$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_time_rules.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/localtime" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_time_rules.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_time_rules.rules"
    # If the audit_time_rules.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/localtime" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/localtime$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/localtime$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Check if watch rule for /etc/localtime
    already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/etc/localtime\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Search /etc/audit/rules.d for
    other rules with specified key audit_time_rules
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_time_rules$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Use /etc/audit/rules.d/audit_time_rules.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_time_rules.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Use matched file as the recipient
    for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Add watch rule for /etc/localtime
    in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Check if watch rule for /etc/localtime
    already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/etc/localtime\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Search /etc/audit/rules.d for
    other rules with specified key audit_time_rules
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_time_rules$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Use /etc/audit/rules.d/audit_time_rules.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_time_rules.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Use matched file as the recipient
    for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Add watch rule for /etc/localtime
    in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Check if watch rule for /etc/localtime
    already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/localtime\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Add watch rule for /etc/localtime
    in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Check if watch rule for /etc/localtime
    already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/localtime\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter the localtime File - Add watch rule for /etc/localtime
    in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/etc/localtime -F perm=wa -F key=audit_time_rules
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-89928-6
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.4.2.b
  - PCI-DSSv4-10.6
  - PCI-DSSv4-10.6.3
  - audit_rules_time_watch_localtime
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules localtime 32  oval:ssg-test_audit_rules_time_watch_localtime_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_time_watch_localtime_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/localtime\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules localtime 64  oval:ssg-test_audit_rules_time_watch_localtime_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_time_watch_localtime_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/localtime\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl localtime 32  oval:ssg-test_audit_rules_time_watch_localtime_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_time_watch_localtime_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/localtime\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl localtime 64  oval:ssg-test_audit_rules_time_watch_localtime_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_time_watch_localtime_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/localtime\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Make the auditd Configuration Immutablexccdf_org.ssgproject.content_rule_audit_rules_immutable mediumCCE-89816-3

Make the auditd Configuration Immutable

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_immutable
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_immutable:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89816-3

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO01.06, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.3.1, 3.4.3
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.310(a)(2)(iv), 164.312(d), 164.310(d)(2)(iii), 164.312(b), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistAC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, ID.SC-4, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4
pcidssReq-10.5.2
os-srgSRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029
app-srg-ctrSRG-APP-000119-CTR-000245, SRG-APP-000120-CTR-000250
anssiR73
cis6.3.3.20
pcidss410.3.2, 10.3
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d in order to make the auditd configuration immutable:
-e 2
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file in order to make the auditd configuration immutable:
-e 2
With this setting, a reboot will be required to change any audit rules.
Rationale
Making the audit configuration immutable prevents accidental as well as malicious modification of the audit rules, although it may be problematic if legitimate changes are needed during system operation.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Traverse all of:
#
# /etc/audit/audit.rules,			(for auditctl case)
# /etc/audit/rules.d/*.rules			(for augenrules case)
#
# files to check if '-e .*' setting is present in that '*.rules' file already.
# If found, delete such occurrence since auditctl(8) manual page instructs the
# '-e 2' rule should be placed as the last rule in the configuration
find /etc/audit /etc/audit/rules.d -maxdepth 1 -type f -name '*.rules' -exec sed -i '/-e[[:space:]]\+.*/d' {} ';'

# Append '-e 2' requirement at the end of both:
# * /etc/audit/audit.rules file 		(for auditctl case)
# * /etc/audit/rules.d/immutable.rules		(for augenrules case)

for AUDIT_FILE in "/etc/audit/audit.rules" "/etc/audit/rules.d/immutable.rules"
do
	echo '' >> $AUDIT_FILE
	echo '# Set the audit.rules configuration immutable per security requirements' >> $AUDIT_FILE
	echo '# Reboot is required to change audit rules once this setting is applied' >> $AUDIT_FILE
	echo '-e 2' >> $AUDIT_FILE
	chmod o-rwx $AUDIT_FILE
	chmod g-rwx $AUDIT_FILE
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89816-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-171-3.4.3
  - NIST-800-53-AC-6(9)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - audit_rules_immutable
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Collect all files from /etc/audit/rules.d with .rules extension
  find:
    paths: /etc/audit/rules.d/
    patterns: '*.rules'
  register: find_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89816-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-171-3.4.3
  - NIST-800-53-AC-6(9)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - audit_rules_immutable
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Remove the -e option from all Audit config files
  lineinfile:
    path: '{{ item }}'
    regexp: ^\s*(?:-e)\s+.*$
    state: absent
  loop: '{{ find_rules_d.files | map(attribute=''path'') | list + [''/etc/audit/audit.rules'']
    }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89816-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-171-3.4.3
  - NIST-800-53-AC-6(9)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - audit_rules_immutable
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Add Audit -e option into /etc/audit/rules.d/immutable.rules and /etc/audit/audit.rules
  lineinfile:
    path: '{{ item }}'
    create: true
    line: -e 2
    mode: g-rwx,o-rwx
  loop:
  - /etc/audit/audit.rules
  - /etc/audit/rules.d/immutable.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89816-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-171-3.4.3
  - NIST-800-53-AC-6(9)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - audit_rules_immutable
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,-e%202%0A
        mode: 0600
        path: /etc/audit/rules.d/90-immutable.rules
        overwrite: true
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules configuration locked  oval:ssg-test_ari_locked_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_ari_locked_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^\-e\s+2\s*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl configuration locked  oval:ssg-test_ari_locked_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_ari_locked_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^\-e\s+2\s*$1
Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)xccdf_org.ssgproject.content_rule_audit_rules_mac_modification_etc_selinux mediumCCE-90737-8

Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_mac_modification_etc_selinux
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_mac_modification_etc_selinux:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90737-8

References:
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
pcidssReq-10.5.5
cis6.3.3.14
pcidss410.3.4, 10.3
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy
-a always,exit -F arch=b64 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy
-a always,exit -F arch=b64 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy
Rationale
The system's mandatory access policy (SELinux) should not be arbitrarily changed by anything other than administrator action. All changes to MAC policy should be audited.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/etc/selinux/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/etc/selinux/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/etc/selinux/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/etc/selinux/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/etc/selinux/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/etc/selinux/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/MAC-policy.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+dir=/etc/selinux/" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/MAC-policy.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/MAC-policy.rules"
    # If the MAC-policy.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/etc/selinux/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/etc/selinux/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/etc/selinux/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/MAC-policy.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+dir=/etc/selinux/" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/MAC-policy.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/MAC-policy.rules"
    # If the MAC-policy.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/etc/selinux/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/etc/selinux/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/etc/selinux/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Check if watch rule for /etc/selinux/ already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+dir=/etc/selinux/\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Search /etc/audit/rules.d for other rules with specified key MAC-policy
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)MAC-policy$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Use /etc/audit/rules.d/MAC-policy.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/MAC-policy.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Add watch rule for /etc/selinux/ in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Check if watch rule for /etc/selinux/ already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+dir=/etc/selinux/\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Search /etc/audit/rules.d for other rules with specified key MAC-policy
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)MAC-policy$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Use /etc/audit/rules.d/MAC-policy.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/MAC-policy.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Add watch rule for /etc/selinux/ in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Check if watch rule for /etc/selinux/ already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+dir=/etc/selinux/\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Add watch rule for /etc/selinux/ in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Check if watch rule for /etc/selinux/ already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+dir=/etc/selinux/\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls (/etc/selinux)
    - Add watch rule for /etc/selinux/ in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F dir=/etc/selinux/ -F perm=wa -F key=MAC-policy
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-90737-8
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_mac_modification_etc_selinux
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules selinux 32  oval:ssg-test_audit_rules_mac_modification_etc_selinux_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_mac_modification_etc_selinux_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+dir=\/etc\/selinux\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules selinux 64  oval:ssg-test_audit_rules_mac_modification_etc_selinux_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_mac_modification_etc_selinux_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+dir=\/etc\/selinux\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl selinux 32  oval:ssg-test_audit_rules_mac_modification_etc_selinux_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_mac_modification_etc_selinux_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+dir=\/etc\/selinux\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl selinux 64  oval:ssg-test_audit_rules_mac_modification_etc_selinux_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_mac_modification_etc_selinux_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+dir=\/etc\/selinux\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Events that Modify the System's Mandatory Access Controls in usr/sharexccdf_org.ssgproject.content_rule_audit_rules_mac_modification_usr_share mediumCCE-88117-7

Record Events that Modify the System's Mandatory Access Controls in usr/share

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_mac_modification_usr_share
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_mac_modification_usr_share:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88117-7

References:
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.8
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
cis6.3.3.14
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy
-a always,exit -F arch=b64 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy
-a always,exit -F arch=b64 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy
Rationale
The system's mandatory access policy (SELinux) should not be arbitrarily changed by anything other than administrator action. All changes to MAC policy should be audited.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/usr/share/selinux/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/usr/share/selinux/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/usr/share/selinux/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/usr/share/selinux/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/usr/share/selinux/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/usr/share/selinux/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/MAC-policy.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+dir=/usr/share/selinux/" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/MAC-policy.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/MAC-policy.rules"
    # If the MAC-policy.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/usr/share/selinux/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/usr/share/selinux/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/usr/share/selinux/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/MAC-policy.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+dir=/usr/share/selinux/" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/MAC-policy.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/MAC-policy.rules"
    # If the MAC-policy.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/usr/share/selinux/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/usr/share/selinux/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/usr/share/selinux/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Check if watch rule for /usr/share/selinux/ already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+dir=/usr/share/selinux/\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Search /etc/audit/rules.d for other rules with specified key MAC-policy
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)MAC-policy$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Use /etc/audit/rules.d/MAC-policy.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/MAC-policy.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Add watch rule for /usr/share/selinux/ in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Check if watch rule for /usr/share/selinux/ already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+dir=/usr/share/selinux/\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Search /etc/audit/rules.d for other rules with specified key MAC-policy
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)MAC-policy$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Use /etc/audit/rules.d/MAC-policy.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/MAC-policy.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Add watch rule for /usr/share/selinux/ in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Check if watch rule for /usr/share/selinux/ already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+dir=/usr/share/selinux/\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Add watch rule for /usr/share/selinux/ in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Check if watch rule for /usr/share/selinux/ already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+dir=/usr/share/selinux/\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Mandatory Access Controls in usr/share
    - Add watch rule for /usr/share/selinux/ in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F dir=/usr/share/selinux/ -F perm=wa -F key=MAC-policy
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-88117-7
  - NIST-800-171-3.1.8
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - audit_rules_mac_modification_usr_share
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules selinux 32  oval:ssg-test_audit_rules_mac_modification_usr_share_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_mac_modification_usr_share_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+dir=\/usr\/share\/selinux\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules selinux 64  oval:ssg-test_audit_rules_mac_modification_usr_share_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_mac_modification_usr_share_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+dir=\/usr\/share\/selinux\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl selinux 32  oval:ssg-test_audit_rules_mac_modification_usr_share_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_mac_modification_usr_share_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+dir=\/usr\/share\/selinux\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl selinux 64  oval:ssg-test_audit_rules_mac_modification_usr_share_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_mac_modification_usr_share_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+dir=\/usr\/share\/selinux\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Ensure auditd Collects Information on Exporting to Media (successful)xccdf_org.ssgproject.content_rule_audit_rules_media_export mediumCCE-86590-7

Ensure auditd Collects Information on Exporting to Media (successful)

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_media_export
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_media_export:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-86590-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.7
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215
app-srg-ctrSRG-APP-000495-CTR-001235
anssiR73
cis6.3.3.10
pcidss410.2.1.7, 10.2.1, 10.2
Description
At a minimum, the audit system should collect media exportation events for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S mount -F auid>=1000 -F auid!=unset -F key=export
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S mount -F auid>=1000 -F auid!=unset -F key=export
Rationale
The unauthorized exportation of data to external media could result in an information leak where classified information, Privacy Act information, and intellectual property could be lost. An audit trail should be created each time a filesystem is mounted to help identify and guard against information loss.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
	SYSCALL="mount"
	KEY="export"
	SYSCALL_GROUPING=""

	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86590-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_media_export
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit mount tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-86590-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_media_export
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for mount for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - mount
      syscall_grouping: []

  - name: Check existence of mount in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/export.rules
    set_fact: audit_file="/etc/audit/rules.d/export.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=export
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - mount
      syscall_grouping: []

  - name: Check existence of mount in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=export
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86590-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_media_export
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for mount for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - mount
      syscall_grouping: []

  - name: Check existence of mount in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/export.rules
    set_fact: audit_file="/etc/audit/rules.d/export.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
        |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=export
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - mount
      syscall_grouping: []

  - name: Check existence of mount in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
        key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
        -F auid!=unset -F key=export
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-86590-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.7
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.7
  - audit_rules_media_export
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit mount  oval:ssg-test_32bit_ardm_mount_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_mount_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+mount[\s]+|([\s]+|[,])mount([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit mount  oval:ssg-test_64bit_ardm_mount_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_mount_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+mount[\s]+|([\s]+|[,])mount([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit mount  oval:ssg-test_32bit_ardm_mount_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_mount_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+mount[\s]+|([\s]+|[,])mount([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit mount  oval:ssg-test_64bit_ardm_mount_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_mount_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+mount[\s]+|([\s]+|[,])mount([\s]+|[,])))(?:.*-F\s+auid>=1000[\s]+)(?:.*-F\s+auid!=(?:4294967295|unset)[\s]+).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Network Environmentxccdf_org.ssgproject.content_rule_audit_rules_networkconfig_modification mediumCCE-87775-3

Record Events that Modify the System's Network Environment

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_networkconfig_modification
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_networkconfig_modification:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-87775-3

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.5.5
anssiR73
cis6.3.3.5
pcidss410.3.4, 10.3
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S sethostname,setdomainname -F key=audit_rules_networkconfig_modification
-w /etc/issue -p wa -k audit_rules_networkconfig_modification
-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
-w /etc/hosts -p wa -k audit_rules_networkconfig_modification

-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules file, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S sethostname,setdomainname -F key=audit_rules_networkconfig_modification
-w /etc/issue -p wa -k audit_rules_networkconfig_modification
-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
-w /etc/hosts -p wa -k audit_rules_networkconfig_modification
-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
Rationale
The network environment should not be modified by anything other than administrator action. Any change to network parameters should be audited.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS=""
	AUID_FILTERS=""
	SYSCALL="sethostname setdomainname"
	KEY="audit_rules_networkconfig_modification"
	SYSCALL_GROUPING="sethostname setdomainname"
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

# Then perform the remediations for the watch rules
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-w[\s]+/etc/issue" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-w$sp\+/etc/issue$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-w /etc/issue -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/issue" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
    # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-w[\s]+/etc/issue" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-w$sp\+/etc/issue$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-w /etc/issue -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-w[\s]+/etc/issue.net" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue.net $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-w$sp\+/etc/issue.net$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/issue.net" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
    # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-w[\s]+/etc/issue.net" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue.net $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-w$sp\+/etc/issue.net$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-w[\s]+/etc/hosts" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/hosts $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-w$sp\+/etc/hosts$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-w /etc/hosts -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/hosts" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
    # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-w[\s]+/etc/hosts" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/hosts $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-w$sp\+/etc/hosts$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-w /etc/hosts -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"

    fi
done

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-w[\s]+/etc/sysconfig/network" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sysconfig/network $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-w$sp\+/etc/sysconfig/network$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/sysconfig/network" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
    # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-w[\s]+/etc/sysconfig/network" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sysconfig/network $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-w$sp\+/etc/sysconfig/network$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set architecture for audit tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Remediate audit rules for network configuration for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - sethostname
      - setdomainname
      syscall_grouping:
      - sethostname
      - setdomainname

  - name: Check existence of sethostname, setdomainname in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
    set_fact: audit_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_rules_networkconfig_modification
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - sethostname
      - setdomainname
      syscall_grouping:
      - sethostname
      - setdomainname

  - name: Check existence of sethostname, setdomainname in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_rules_networkconfig_modification
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Remediate audit rules for network configuration for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - sethostname
      - setdomainname
      syscall_grouping:
      - sethostname
      - setdomainname

  - name: Check existence of sethostname, setdomainname in /etc/audit/rules.d/
    find:
      paths: /etc/audit/rules.d
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: '*.rules'
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Reset syscalls found per file
    set_fact:
      syscalls_per_file: {}
      found_paths_dict: {}

  - name: Declare syscalls found per file
    set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
      :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
    loop: '{{ find_command.results | selectattr(''matched'') | list }}'

  - name: Declare files where syscalls were found
    set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
      | map(attribute='path') | list }}"

  - name: Count occurrences of syscalls in paths
    set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
      0) }) }}"
    loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
      | list }}'

  - name: Get path with most syscalls
    set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
      | last).key }}"
    when: found_paths | length >= 1

  - name: No file with syscall found, set path to /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
    set_fact: audit_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
    when: found_paths | length == 0

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
        | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_rules_networkconfig_modification
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - sethostname
      - setdomainname
      syscall_grouping:
      - sethostname
      - setdomainname

  - name: Check existence of sethostname, setdomainname in /etc/audit/audit.rules
    find:
      paths: /etc/audit
      contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
        |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
      patterns: audit.rules
    register: find_command
    loop: '{{ (syscall_grouping + syscalls) | unique }}'

  - name: Set path to /etc/audit/audit.rules
    set_fact: audit_file="/etc/audit/audit.rules"

  - name: Declare found syscalls
    set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
      | list }}"

  - name: Declare missing syscalls
    set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

  - name: Replace the audit rule in {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
        join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
      line: \1\2\3{{ missing_syscalls | join("\3") }}\4
      backrefs: true
      state: present
      mode: g-rwx,o-rwx
    when: syscalls_found | length > 0 and missing_syscalls | length > 0

  - name: Add the audit rule to {{ audit_file }}
    lineinfile:
      path: '{{ audit_file }}'
      line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_rules_networkconfig_modification
      create: true
      mode: g-rwx,o-rwx
      state: present
    when: syscalls_found | length == 0
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - audit_arch == "b64"
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/issue already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-w\s+/etc/issue\s+-p\s+wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_networkconfig_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use matched file
    as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/issue in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -w /etc/issue -p wa -k audit_rules_networkconfig_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/issue already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-w\s+/etc/issue\s+-p\s+wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/issue in /etc/audit/audit.rules
  lineinfile:
    line: -w /etc/issue -p wa -k audit_rules_networkconfig_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/issue.net already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-w\s+/etc/issue.net\s+-p\s+wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_networkconfig_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use matched file
    as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/issue.net in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/issue.net already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-w\s+/etc/issue.net\s+-p\s+wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/issue.net in /etc/audit/audit.rules
  lineinfile:
    line: -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/hosts already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-w\s+/etc/hosts\s+-p\s+wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_networkconfig_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use matched file
    as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/hosts in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/hosts already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-w\s+/etc/hosts\s+-p\s+wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/hosts in /etc/audit/audit.rules
  lineinfile:
    line: -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/sysconfig/network already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-w\s+/etc/sysconfig/network\s+-p\s+wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_networkconfig_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use matched file
    as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/sysconfig/network in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/sysconfig/network already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-w\s+/etc/sysconfig/network\s+-p\s+wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/sysconfig/network in /etc/audit/audit.rules
  lineinfile:
    line: -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-87775-3
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.5
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.4
  - audit_rules_networkconfig_modification
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit /etc/issue augenrules  oval:ssg-test_arnm_etc_issue_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_arnm_etc_issue_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^\-w[\s]+/etc/issue[\s]+\-p[\s]+\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$1

audit /etc/issue.net augenrules  oval:ssg-test_arnm_etc_issue_net_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_arnm_etc_issue_net_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^\-w[\s]+/etc/issue\.net[\s]+\-p[\s]+\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$1

audit /etc/hosts augenrules  oval:ssg-test_arnm_etc_hosts_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_arnm_etc_hosts_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^\-w[\s]+/etc/hosts[\s]+\-p[\s]+\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$1

audit /etc/sysconfig/network augenrules  oval:ssg-test_arnm_etc_sysconfig_network_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_arnm_etc_sysconfig_network_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^\-w[\s]+/etc/sysconfig/network[\s]+\-p[\s]+\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$1

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit sethostname  oval:ssg-test_32bit_ardm_sethostname_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_sethostname_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+sethostname[\s]+|([\s]+|[,])sethostname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit sethostname  oval:ssg-test_64bit_ardm_sethostname_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_sethostname_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+sethostname[\s]+|([\s]+|[,])sethostname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit sethostname  oval:ssg-test_32bit_ardm_sethostname_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_sethostname_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+sethostname[\s]+|([\s]+|[,])sethostname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit sethostname  oval:ssg-test_64bit_ardm_sethostname_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_sethostname_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+sethostname[\s]+|([\s]+|[,])sethostname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit setdomainname  oval:ssg-test_32bit_ardm_setdomainname_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_setdomainname_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+setdomainname[\s]+|([\s]+|[,])setdomainname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit setdomainname  oval:ssg-test_64bit_ardm_setdomainname_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_setdomainname_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+setdomainname[\s]+|([\s]+|[,])setdomainname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit setdomainname  oval:ssg-test_32bit_ardm_setdomainname_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_setdomainname_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+setdomainname[\s]+|([\s]+|[,])setdomainname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit setdomainname  oval:ssg-test_64bit_ardm_setdomainname_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_setdomainname_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+setdomainname[\s]+|([\s]+|[,])setdomainname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit /etc/issue auditctl  oval:ssg-test_arnm_etc_issue_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_arnm_etc_issue_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^\-w[\s]+/etc/issue[\s]+\-p[\s]+\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$1

audit /etc/issue.net auditctl  oval:ssg-test_arnm_etc_issue_net_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_arnm_etc_issue_net_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^\-w[\s]+/etc/issue\.net[\s]+\-p[\s]+\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$1

audit /etc/hosts auditctl  oval:ssg-test_arnm_etc_hosts_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_arnm_etc_hosts_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^\-w[\s]+/etc/hosts[\s]+\-p[\s]+\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$1

audit /etc/sysconfig/network auditctl  oval:ssg-test_arnm_etc_sysconfig_network_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_arnm_etc_sysconfig_network_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^\-w[\s]+/etc/sysconfig/network[\s]+\-p[\s]+\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$1

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit sethostname  oval:ssg-test_32bit_ardm_sethostname_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_sethostname_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+sethostname[\s]+|([\s]+|[,])sethostname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit sethostname  oval:ssg-test_64bit_ardm_sethostname_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_sethostname_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+sethostname[\s]+|([\s]+|[,])sethostname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit sethostname  oval:ssg-test_32bit_ardm_sethostname_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_sethostname_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+sethostname[\s]+|([\s]+|[,])sethostname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit sethostname  oval:ssg-test_64bit_ardm_sethostname_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_sethostname_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+sethostname[\s]+|([\s]+|[,])sethostname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit setdomainname  oval:ssg-test_32bit_ardm_setdomainname_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_setdomainname_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+setdomainname[\s]+|([\s]+|[,])setdomainname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit augenrules 64-bit setdomainname  oval:ssg-test_64bit_ardm_setdomainname_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_setdomainname_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+setdomainname[\s]+|([\s]+|[,])setdomainname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit setdomainname  oval:ssg-test_32bit_ardm_setdomainname_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_ardm_setdomainname_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b32[\s]+)(?:.*(-S[\s]+setdomainname[\s]+|([\s]+|[,])setdomainname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

64 bit architecture  oval:ssg-test_system_info_architecture_x86_64:tst:1  true

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
truex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppc_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_ppcle_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_aarch_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

64 bit architecture  oval:ssg-test_system_info_architecture_s390_64:tst:1  false

Following items have been found on the system:
Result of item-state comparisonMachine classNode nameOs nameOs releaseOs versionProcessor type
falsex86_64ip-172-31-7-17.eu-north-1.compute.internalLinux6.12.0-55.22.1.el10_0.x86_64#1 SMP PREEMPT_DYNAMIC Mon Jul 14 04:43:00 EDT 2025x86_64

audit auditctl 64-bit setdomainname  oval:ssg-test_64bit_ardm_setdomainname_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_ardm_setdomainname_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+(?:.*-F[\s]+arch=b64[\s]+)(?:.*(-S[\s]+setdomainname[\s]+|([\s]+|[,])setdomainname([\s]+|[,]))).*(-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Record Events that Modify the System's Network Environmentxccdf_org.ssgproject.content_rule_audit_rules_networkconfig_modification_network_scripts mediumCCE-90731-1

Record Events that Modify the System's Network Environment

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_networkconfig_modification_network_scripts
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_networkconfig_modification_network_scripts:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90731-1

References:
cis6.3.3.5
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/etc/sysconfig/network-scripts -F perm=wa -F key=audit_rules_networkconfig_modification_network_scripts
-a always,exit -F arch=b64 -F path=/etc/sysconfig/network-scripts -F perm=wa -F key=audit_rules_networkconfig_modification_network_scripts
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/etc/sysconfig/network-scripts -F perm=wa -F key=audit_rules_networkconfig_modification_network_scripts
-a always,exit -F arch=b64 -F path=/etc/sysconfig/network-scripts -F perm=wa -F key=audit_rules_networkconfig_modification_network_scripts
Rationale
The network environment should not be modified by anything other than administrator action. Any change to network parameters should be audited.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/sysconfig/network-scripts" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/sysconfig/network-scripts$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/sysconfig/network-scripts$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/sysconfig/network-scripts -F perm=wa -F key=audit_rules_networkconfig_modification_network_scripts" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/sysconfig/network-scripts" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/sysconfig/network-scripts$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/sysconfig/network-scripts$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/sysconfig/network-scripts -F perm=wa -F key=audit_rules_networkconfig_modification_network_scripts" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/sysconfig/network-scripts" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules"
    # If the audit_rules_networkconfig_modification_network_scripts.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/sysconfig/network-scripts" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/sysconfig/network-scripts$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/sysconfig/network-scripts$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/sysconfig/network-scripts -F perm=wa -F key=audit_rules_networkconfig_modification_network_scripts" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/sysconfig/network-scripts" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules"
    # If the audit_rules_networkconfig_modification_network_scripts.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/sysconfig/network-scripts" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/sysconfig/network-scripts$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/sysconfig/network-scripts$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/sysconfig/network-scripts -F perm=wa -F key=audit_rules_networkconfig_modification_network_scripts" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/sysconfig/network-scripts already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/etc/sysconfig/network-scripts\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_networkconfig_modification_network_scripts
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification_network_scripts$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use /etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use matched file
    as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/sysconfig/network-scripts in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/etc/sysconfig/network-scripts -F perm=wa
      -F key=audit_rules_networkconfig_modification_network_scripts
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/sysconfig/network-scripts already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/etc/sysconfig/network-scripts\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_networkconfig_modification_network_scripts
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification_network_scripts$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use /etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_networkconfig_modification_network_scripts.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Use matched file
    as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/sysconfig/network-scripts in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/etc/sysconfig/network-scripts -F perm=wa
      -F key=audit_rules_networkconfig_modification_network_scripts
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/sysconfig/network-scripts already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/sysconfig/network-scripts\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/sysconfig/network-scripts in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/etc/sysconfig/network-scripts -F perm=wa
      -F key=audit_rules_networkconfig_modification_network_scripts
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Check if watch
    rule for /etc/sysconfig/network-scripts already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/sysconfig/network-scripts\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify the System's Network Environment - Add watch rule
    for /etc/sysconfig/network-scripts in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/etc/sysconfig/network-scripts -F perm=wa
      -F key=audit_rules_networkconfig_modification_network_scripts
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-90731-1
  - audit_rules_networkconfig_modification_network_scripts
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules network_scripts 32  oval:ssg-test_audit_rules_networkconfig_modification_network_scripts_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_networkconfig_modification_network_scripts_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/sysconfig\/network-scripts\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules network_scripts 64  oval:ssg-test_audit_rules_networkconfig_modification_network_scripts_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_networkconfig_modification_network_scripts_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/sysconfig\/network-scripts\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl network_scripts 32  oval:ssg-test_audit_rules_networkconfig_modification_network_scripts_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_networkconfig_modification_network_scripts_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/sysconfig\/network-scripts\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl network_scripts 64  oval:ssg-test_audit_rules_networkconfig_modification_network_scripts_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_networkconfig_modification_network_scripts_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/sysconfig\/network-scripts\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Attempts to Alter Process and Session Initiation Information btmpxccdf_org.ssgproject.content_rule_audit_rules_session_events_btmp mediumCCE-86190-6

Record Attempts to Alter Process and Session Initiation Information btmp

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_session_events_btmp
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_session_events_btmp:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-86190-6

References:
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
nistAU-12(c), AU-12.1(iv)
os-srgSRG-OS-000472-GPOS-00217
anssiR73
cis6.3.3.11
pcidss410.2.1.3, 10.2.1, 10.2
Description
The audit system already collects process information for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/var/log/btmp -F perm=wa -F key=session
-a always,exit -F arch=b64 -F path=/var/log/btmp -F perm=wa -F key=session
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/var/log/btmp -F perm=wa -F key=session
-a always,exit -F arch=b64 -F path=/var/log/btmp -F perm=wa -F key=session
Rationale
Manual editing of these files may indicate nefarious activity, such as an attacker attempting to remove evidence of an intrusion.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/btmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/btmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/btmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/var/log/btmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/btmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/btmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/btmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/var/log/btmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/var/log/btmp" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/session.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/session.rules"
    # If the session.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/btmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/btmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/btmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/var/log/btmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/var/log/btmp" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/session.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/session.rules"
    # If the session.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/btmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/btmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/btmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/var/log/btmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Check if watch rule for /var/log/btmp already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/var/log/btmp\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Search /etc/audit/rules.d for other rules with specified key session
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)session$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Use /etc/audit/rules.d/session.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/session.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Add watch rule for /var/log/btmp in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/var/log/btmp -F perm=wa -F key=session
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Check if watch rule for /var/log/btmp already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/var/log/btmp\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Search /etc/audit/rules.d for other rules with specified key session
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)session$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Use /etc/audit/rules.d/session.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/session.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Add watch rule for /var/log/btmp in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/var/log/btmp -F perm=wa -F key=session
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Check if watch rule for /var/log/btmp already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/var/log/btmp\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Add watch rule for /var/log/btmp in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/var/log/btmp -F perm=wa -F key=session
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Check if watch rule for /var/log/btmp already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/var/log/btmp\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information btmp -
    Add watch rule for /var/log/btmp in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/var/log/btmp -F perm=wa -F key=session
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-86190-6
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_btmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules btmp 32  oval:ssg-test_audit_rules_session_events_btmp_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_btmp_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/var\/log\/btmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules btmp 64  oval:ssg-test_audit_rules_session_events_btmp_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_btmp_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/var\/log\/btmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl btmp 32  oval:ssg-test_audit_rules_session_events_btmp_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_btmp_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/var\/log\/btmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl btmp 64  oval:ssg-test_audit_rules_session_events_btmp_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_btmp_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/var\/log\/btmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Attempts to Alter Process and Session Initiation Information utmpxccdf_org.ssgproject.content_rule_audit_rules_session_events_utmp mediumCCE-86193-0

Record Attempts to Alter Process and Session Initiation Information utmp

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_session_events_utmp
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_session_events_utmp:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-86193-0

References:
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
nistAU-12(c), AU-12.1(iv)
os-srgSRG-OS-000472-GPOS-00217
anssiR73
cis6.3.3.11
pcidss410.2.1.3, 10.2.1, 10.2
Description
The audit system already collects process information for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/var/run/utmp -F perm=wa -F key=session
-a always,exit -F arch=b64 -F path=/var/run/utmp -F perm=wa -F key=session
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/var/run/utmp -F perm=wa -F key=session
-a always,exit -F arch=b64 -F path=/var/run/utmp -F perm=wa -F key=session
Rationale
Manual editing of these files may indicate nefarious activity, such as an attacker attempting to remove evidence of an intrusion.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/run/utmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/run/utmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/run/utmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/var/run/utmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/run/utmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/run/utmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/run/utmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/var/run/utmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/var/run/utmp" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/session.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/session.rules"
    # If the session.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/run/utmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/run/utmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/run/utmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/var/run/utmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/var/run/utmp" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/session.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/session.rules"
    # If the session.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/run/utmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/run/utmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/run/utmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/var/run/utmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Check if watch rule for /var/run/utmp already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/var/run/utmp\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Search /etc/audit/rules.d for other rules with specified key session
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)session$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Use /etc/audit/rules.d/session.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/session.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Add watch rule for /var/run/utmp in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/var/run/utmp -F perm=wa -F key=session
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Check if watch rule for /var/run/utmp already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/var/run/utmp\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Search /etc/audit/rules.d for other rules with specified key session
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)session$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Use /etc/audit/rules.d/session.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/session.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Add watch rule for /var/run/utmp in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/var/run/utmp -F perm=wa -F key=session
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Check if watch rule for /var/run/utmp already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/var/run/utmp\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Add watch rule for /var/run/utmp in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/var/run/utmp -F perm=wa -F key=session
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Check if watch rule for /var/run/utmp already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/var/run/utmp\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information utmp -
    Add watch rule for /var/run/utmp in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/var/run/utmp -F perm=wa -F key=session
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-86193-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_utmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules utmp 32  oval:ssg-test_audit_rules_session_events_utmp_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_utmp_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/var\/run\/utmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules utmp 64  oval:ssg-test_audit_rules_session_events_utmp_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_utmp_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/var\/run\/utmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl utmp 32  oval:ssg-test_audit_rules_session_events_utmp_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_utmp_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/var\/run\/utmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl utmp 64  oval:ssg-test_audit_rules_session_events_utmp_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_utmp_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/var\/run\/utmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Attempts to Alter Process and Session Initiation Information wtmpxccdf_org.ssgproject.content_rule_audit_rules_session_events_wtmp mediumCCE-86206-0

Record Attempts to Alter Process and Session Initiation Information wtmp

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_session_events_wtmp
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_session_events_wtmp:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-86206-0

References:
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
nistAU-12(c), AU-12.1(iv)
os-srgSRG-OS-000472-GPOS-00217
anssiR73
cis6.3.3.11
pcidss410.2.1.3, 10.2.1, 10.2
Description
The audit system already collects process information for all users and root. If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/var/log/wtmp -F perm=wa -F key=session
-a always,exit -F arch=b64 -F path=/var/log/wtmp -F perm=wa -F key=session
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/var/log/wtmp -F perm=wa -F key=session
-a always,exit -F arch=b64 -F path=/var/log/wtmp -F perm=wa -F key=session
Rationale
Manual editing of these files may indicate nefarious activity, such as an attacker attempting to remove evidence of an intrusion.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/wtmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/wtmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/wtmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/var/log/wtmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/wtmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/wtmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/wtmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/var/log/wtmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/var/log/wtmp" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/session.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/session.rules"
    # If the session.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/wtmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/wtmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/wtmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/var/log/wtmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/var/log/wtmp" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/session.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/session.rules"
    # If the session.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/wtmp" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/wtmp$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/wtmp$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/var/log/wtmp -F perm=wa -F key=session" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Check if watch rule for /var/log/wtmp already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/var/log/wtmp\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Search /etc/audit/rules.d for other rules with specified key session
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)session$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Use /etc/audit/rules.d/session.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/session.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Add watch rule for /var/log/wtmp in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/var/log/wtmp -F perm=wa -F key=session
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Check if watch rule for /var/log/wtmp already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/var/log/wtmp\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Search /etc/audit/rules.d for other rules with specified key session
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)session$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Use /etc/audit/rules.d/session.rules as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/session.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Add watch rule for /var/log/wtmp in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/var/log/wtmp -F perm=wa -F key=session
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Check if watch rule for /var/log/wtmp already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/var/log/wtmp\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Add watch rule for /var/log/wtmp in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/var/log/wtmp -F perm=wa -F key=session
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Check if watch rule for /var/log/wtmp already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/var/log/wtmp\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to Alter Process and Session Initiation Information wtmp -
    Add watch rule for /var/log/wtmp in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/var/log/wtmp -F perm=wa -F key=session
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-86206-0
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-12.1(iv)
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_rules_session_events_wtmp
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules wtmp 32  oval:ssg-test_audit_rules_session_events_wtmp_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_wtmp_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/var\/log\/wtmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules wtmp 64  oval:ssg-test_audit_rules_session_events_wtmp_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_wtmp_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/var\/log\/wtmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl wtmp 32  oval:ssg-test_audit_rules_session_events_wtmp_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_wtmp_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/var\/log\/wtmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl wtmp 64  oval:ssg-test_audit_rules_session_events_wtmp_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_session_events_wtmp_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/var\/log\/wtmp\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Events When Executables Are Run As Another Userxccdf_org.ssgproject.content_rule_audit_rules_suid_auid_privilege_function mediumCCE-86599-8

Record Events When Executables Are Run As Another User

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_suid_auid_privilege_function
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_suid_auid_privilege_function:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-86599-8

References:
cis6.3.3.2
Description
Verify the system generates an audit record when actions are run as another user. sudo provides users with temporary elevated privileges to perform operations, either as the superuser or another user. If audit is using the "auditctl" tool to load the rules, run the following command:
$ sudo grep execve /etc/audit/audit.rules
If audit is using the "augenrules" tool to load the rules, run the following command:
$ sudo grep -r execve /etc/audit/rules.d
-a always,exit -F arch=b32 -S execve -C euid!=uid -F auid!=unset -k user_emulation
-a always,exit -F arch=b64  S execve -C euid!=uid -F auid!=unset -k user_emulation
If both the "b32" and "b64" audit rules for "SUID" files are not defined, this is a finding.
Rationale
Creating an audit log of users with temporary elevated privileges and the operation(s) they performed is essential to reporting. Administrators will want to correlate the events written to the audit trail with the records written to sudo's logfile to verify if unauthorized commands have been executed. Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised information system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider threats and the advanced persistent threat.
Warnings
warning  Note that these rules can be configured in a number of ways while still achieving the desired effect.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

for ARCH in "${RULE_ARCHS[@]}"
do
	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
	OTHER_FILTERS="-C euid!=uid"
	AUID_FILTERS="-F auid!=unset"
	SYSCALL="execve"
	KEY="user_emulation"
	SYSCALL_GROUPING=""
	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()

# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
    file_to_inspect="/etc/audit/rules.d/$KEY.rules"
    files_to_inspect=("$file_to_inspect")
    if [ ! -e "$file_to_inspect" ]
    then
        touch "$file_to_inspect"
        chmod 0600 "$file_to_inspect"
    fi
fi

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
	unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule

# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING

# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
#  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
# -----------------------------------------------------------------------------------------
#        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
# -----------------------------------------------------------------------------------------
#        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
#        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()


# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )

# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1

for audit_file in "${files_to_inspect[@]}"
do
    # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
    # i.e, collect rules that match:
    # * the action, list and arch, (2-nd argument)
    # * the other filters, (3-rd argument)
    # * the auid filters, (4-rd argument)
    readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

    candidate_rules=()
    # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
    for s_rule in "${similar_rules[@]}"
    do
        # Strip all the options and fields we know of,
        # than check if there was any field left over
        extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
        grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
    done

    if [[ ${#syscall_a[@]} -ge 1 ]]
    then
        # Check if the syscall we want is present in any of the similar existing rules
        for rule in "${candidate_rules[@]}"
        do
            rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
            all_syscalls_found=0
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                   # A syscall was not found in the candidate rule
                   all_syscalls_found=1
                   }
            done
            if [[ $all_syscalls_found -eq 0 ]]
            then
                # We found a rule with all the syscall(s) we want; skip rest of macro
                skip=0
                break
            fi

            # Check if this rule can be grouped with our target syscall and keep track of it
            for syscall_g in "${syscall_grouping[@]}"
            do
                if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                then
                    file_to_edit=${audit_file}
                    rule_to_edit=${rule}
                    rule_syscalls_to_edit=${rule_syscalls}
                fi
            done
        done
    else
        # If there is any candidate rule, it is compliant; skip rest of macro
        if [ "${#candidate_rules[@]}" -gt 0 ]
        then
            skip=0
        fi
    fi

    if [ "$skip" -eq 0 ]; then
        break
    fi
done

if [ "$skip" -ne 0 ]; then
    # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
    # At this point we know if we need to either append the $full_rule or group
    # the syscall together with an exsiting rule

    # Append the full_rule if it cannot be grouped to any other rule
    if [ -z ${rule_to_edit+x} ]
    then
        # Build full_rule while avoid adding double spaces when other_filters is empty
        if [ "${#syscall_a[@]}" -gt 0 ]
        then
            syscall_string=""
            for syscall in "${syscall_a[@]}"
            do
                syscall_string+=" -S $syscall"
            done
        fi
        other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
        auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
        full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
        echo "$full_rule" >> "$default_file"
        chmod 0600 ${default_file}
    else
        # Check if the syscalls are declared as a comma separated list or
        # as multiple -S parameters
        if grep -q -- "," <<< "${rule_syscalls_to_edit}"
        then
            delimiter=","
        else
            delimiter=" -S "
        fi
        new_grouped_syscalls="${rule_syscalls_to_edit}"
        for syscall in "${syscall_a[@]}"
        do
            grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
               # A syscall was not found in the candidate rule
               new_grouped_syscalls+="${delimiter}${syscall}"
               }
        done

        # Group the syscall in the rule
        sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
    fi
fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86599-8
  - audit_rules_suid_auid_privilege_function
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Service facts
  ansible.builtin.service_facts: null
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86599-8
  - audit_rules_suid_auid_privilege_function
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the rules script being used
  ansible.builtin.command: grep '^ExecStart' /usr/lib/systemd/system/audit-rules.service
  register: check_rules_scripts_result
  changed_when: false
  failed_when: false
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86599-8
  - audit_rules_suid_auid_privilege_function
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set suid_audit_rules fact
  ansible.builtin.set_fact:
    suid_audit_rules:
    - rule: -a always,exit -F arch=b32 -S execve -C euid!=uid -F auid!=unset -k user_emulation
      regex: ^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-C[\s]+euid!=uid[\s]+-F[\s]+auid!=unset[\s]+-S[\s]+execve[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
    - rule: -a always,exit -F arch=b64 -S execve -C euid!=uid -F auid!=unset -k user_emulation
      regex: ^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-C[\s]+euid!=uid[\s]+-F[\s]+auid!=unset[\s]+-S[\s]+execve[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86599-8
  - audit_rules_suid_auid_privilege_function
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Update /etc/audit/rules.d/user_emulation.rules to audit privileged functions
  ansible.builtin.lineinfile:
    path: /etc/audit/rules.d/user_emulation.rules
    line: '{{  item.rule  }}'
    regexp: '{{ item.regex }}'
    create: true
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - '"auditd.service" in ansible_facts.services'
  - '"augenrules" in check_rules_scripts_result.stdout'
  register: augenrules_audit_rules_privilege_function_update_result
  with_items: '{{ suid_audit_rules }}'
  tags:
  - CCE-86599-8
  - audit_rules_suid_auid_privilege_function
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Update Update /etc/audit/audit.rules to audit privileged functions
  ansible.builtin.lineinfile:
    path: /etc/audit/audit.rules
    line: '{{  item.rule  }}'
    regexp: '{{ item.regex }}'
    create: true
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - '"auditd.service" in ansible_facts.services'
  - '"auditctl" in check_rules_scripts_result.stdout'
  register: auditctl_audit_rules_privilege_function_update_result
  with_items: '{{ suid_audit_rules }}'
  tags:
  - CCE-86599-8
  - audit_rules_suid_auid_privilege_function
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Restart Auditd
  ansible.builtin.command: /usr/sbin/service auditd restart
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - (augenrules_audit_rules_privilege_function_update_result.changed or auditctl_audit_rules_privilege_function_update_result.changed)
  - ansible_facts.services["auditd.service"].state == "running"
  tags:
  - CCE-86599-8
  - audit_rules_suid_auid_privilege_function
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules 32-bit uid privileged function  oval:ssg-test_32bit_uid_auid_privileged_function_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_uid_auid_privileged_function_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-S[\s]+execve[\s]+-C[\s]+euid!=uid[\s]+-F[\s]+auid!=unset[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit augenrules 64-bit uid privileged function  oval:ssg-test_64bit_uid_auid_privileged_function_augenrules:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_uid_auid_privileged_function_augenrules:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^/etc/audit/rules\.d/.*\.rules$^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-S[\s]+execve[\s]+-C[\s]+euid!=uid[\s]+-F[\s]+auid!=unset[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl 32-bit uid privileged function  oval:ssg-test_32bit_uid_auid_privileged_function_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_32bit_uid_auid_privileged_function_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b32[\s]+-S[\s]+execve[\s]+-C[\s]+euid!=uid[\s]+-F[\s]+auid!=unset[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1

audit auditctl 64-bit uid privileged_function  oval:ssg-test_64bit_uid_auid_privileged_function_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_64bit_uid_auid_privileged_function_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/audit/audit.rules^[\s]*-a[\s]+always,exit[\s]+-F[\s]+arch=b64[\s]+-S[\s]+execve[\s]+-C[\s]+euid!=uid[\s]+-F[\s]+auid!=unset[\s]+(?:-k[\s]+|-F[\s]+key=)[\S]+[\s]*$1
Ensure auditd Collects System Administrator Actionsxccdf_org.ssgproject.content_rule_audit_rules_sysadmin_actions mediumCCE-89678-7

Ensure auditd Collects System Administrator Actions

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_sysadmin_actions
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_sysadmin_actions:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89678-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistAC-2(7)(b), AU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.2, Req-10.2.5.b
os-srgSRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000304-GPOS-00121, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221
app-srg-ctrSRG-APP-000026-CTR-000070, SRG-APP-000027-CTR-000075, SRG-APP-000028-CTR-000080, SRG-APP-000291-CTR-000675, SRG-APP-000292-CTR-000680, SRG-APP-000293-CTR-000685, SRG-APP-000294-CTR-000690, SRG-APP-000319-CTR-000745, SRG-APP-000320-CTR-000750, SRG-APP-000509-CTR-001305
anssiR73
cis6.3.3.1
pcidss410.2.1.5, 10.2.1, 10.2
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/etc/sudoers -F perm=wa -F key=actions
-a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=actions
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/etc/sudoers -F perm=wa -F key=actions
-a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=actions
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions
-a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions
-a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions
Rationale
The actions taken by system administrators should be audited to keep a record of what was executed on the system, as well as, for accountability purposes.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'


# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/sudoers" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/sudoers$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/sudoers$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/sudoers -F perm=wa -F key=actions" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/sudoers" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/sudoers$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/sudoers$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=actions" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/actions.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/sudoers" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/actions.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/actions.rules"
    # If the actions.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/sudoers" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/sudoers$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/sudoers$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/sudoers -F perm=wa -F key=actions" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/actions.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/sudoers" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/actions.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/actions.rules"
    # If the actions.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/sudoers" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/sudoers$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/sudoers$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=actions" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/etc/sudoers.d/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/etc/sudoers.d/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/etc/sudoers.d/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/etc/sudoers.d/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/etc/sudoers.d/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/etc/sudoers.d/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/actions.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+dir=/etc/sudoers.d/" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/actions.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/actions.rules"
    # If the actions.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/etc/sudoers.d/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/etc/sudoers.d/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+dir=/etc/sudoers.d/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/actions.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+dir=/etc/sudoers.d/" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/actions.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/actions.rules"
    # If the actions.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+dir=/etc/sudoers.d/" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/etc/sudoers.d/$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+dir=/etc/sudoers.d/$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Check if watch rule
    for /etc/sudoers already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/etc/sudoers\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Search /etc/audit/rules.d
    for other rules with specified key actions
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)actions$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Use /etc/audit/rules.d/actions.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/actions.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Use matched file as
    the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Add watch rule for /etc/sudoers
    in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/etc/sudoers -F perm=wa -F key=actions
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Check if watch rule
    for /etc/sudoers already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/etc/sudoers\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Search /etc/audit/rules.d
    for other rules with specified key actions
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)actions$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Use /etc/audit/rules.d/actions.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/actions.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Use matched file as
    the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Add watch rule for /etc/sudoers
    in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=actions
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Check if watch rule
    for /etc/sudoers already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/sudoers\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Add watch rule for /etc/sudoers
    in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/etc/sudoers -F perm=wa -F key=actions
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Check if watch rule
    for /etc/sudoers already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/sudoers\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Add watch rule for /etc/sudoers
    in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=actions
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Check if watch rule
    for /etc/sudoers.d/ already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+dir=/etc/sudoers.d/\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Search /etc/audit/rules.d
    for other rules with specified key actions
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)actions$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Use /etc/audit/rules.d/actions.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/actions.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Use matched file as
    the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Add watch rule for /etc/sudoers.d/
    in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Check if watch rule
    for /etc/sudoers.d/ already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+dir=/etc/sudoers.d/\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Search /etc/audit/rules.d
    for other rules with specified key actions
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)actions$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Use /etc/audit/rules.d/actions.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/actions.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Use matched file as
    the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Add watch rule for /etc/sudoers.d/
    in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Check if watch rule
    for /etc/sudoers.d/ already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+dir=/etc/sudoers.d/\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Add watch rule for /etc/sudoers.d/
    in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Check if watch rule
    for /etc/sudoers.d/ already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+dir=/etc/sudoers.d/\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure auditd Collects System Administrator Actions - Add watch rule for /etc/sudoers.d/
    in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-89678-7
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(7)(b)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_sysadmin_actions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,{{ -w%20/etc/sudoers.d/%20-p%20wa%20-k%20actions%0A-w%20/etc/sudoers%20-p%20wa%20-k%20actions%0A }}
        mode: 0600
        path: /etc/audit/rules.d/75-audit-sysadmin-actions.rules
        overwrite: true
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules sudoers 32  oval:ssg-test_audit_rules_sudoers_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_sudoers_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/sudoers\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules sudoers 64  oval:ssg-test_audit_rules_sudoers_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_sudoers_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/sudoers\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl sudoers 32  oval:ssg-test_audit_rules_sudoers_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_sudoers_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/sudoers\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl sudoers 64  oval:ssg-test_audit_rules_sudoers_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_sudoers_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/sudoers\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules sudoers_d 32  oval:ssg-test_audit_rules_sudoers_d_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_sudoers_d_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+dir=\/etc\/sudoers.d\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules sudoers_d 64  oval:ssg-test_audit_rules_sudoers_d_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_sudoers_d_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+dir=\/etc\/sudoers.d\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl sudoers_d 32  oval:ssg-test_audit_rules_sudoers_d_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_sudoers_d_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+dir=\/etc\/sudoers.d\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl sudoers_d 64  oval:ssg-test_audit_rules_sudoers_d_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_sudoers_d_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+dir=\/etc\/sudoers.d\/\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Events that Modify User/Group Information - /etc/groupxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_group mediumCCE-87111-1

Record Events that Modify User/Group Information - /etc/group

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_group
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_usergroup_modification_group:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-87111-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nistAC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.5
os-srgSRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275
anssiR73
cis6.3.3.8
pcidss410.2.1.5, 10.2.1, 10.2
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification
Rationale
In addition to auditing new user and group accounts, these watches will alert the system administrator(s) to any modifications. Any unexpected users, groups, or modifications should be investigated for legitimacy.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/group" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/group$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/group$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/group" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/group$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/group$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/group" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
    # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/group" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/group$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/group$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/group" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
    # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/group" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/group$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/group$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Check if watch
    rule for /etc/group already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/etc/group\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_usergroup_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Use matched
    file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Add watch
    rule for /etc/group in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Check if watch
    rule for /etc/group already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/etc/group\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_usergroup_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Use matched
    file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Add watch
    rule for /etc/group in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Check if watch
    rule for /etc/group already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/group\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Add watch
    rule for /etc/group in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Check if watch
    rule for /etc/group already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/group\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/group - Add watch
    rule for /etc/group in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F key=audit_rules_usergroup_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-87111-1
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules group 32  oval:ssg-test_audit_rules_usergroup_modification_group_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_group_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/group\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules group 64  oval:ssg-test_audit_rules_usergroup_modification_group_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_group_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/group\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl group 32  oval:ssg-test_audit_rules_usergroup_modification_group_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_group_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/group\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl group 64  oval:ssg-test_audit_rules_usergroup_modification_group_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_group_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/group\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Events that Modify User/Group Information - /etc/gshadowxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_gshadow mediumCCE-87736-5

Record Events that Modify User/Group Information - /etc/gshadow

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_gshadow
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_usergroup_modification_gshadow:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-87736-5

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nistAC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.5
os-srgSRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275
anssiR73
cis6.3.3.8
pcidss410.2.1.5, 10.2.1, 10.2
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification
Rationale
In addition to auditing new user and group accounts, these watches will alert the system administrator(s) to any modifications. Any unexpected users, groups, or modifications should be investigated for legitimacy.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/gshadow" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/gshadow$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/gshadow$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/gshadow" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/gshadow$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/gshadow$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/gshadow" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
    # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/gshadow" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/gshadow$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/gshadow$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/gshadow" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
    # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/gshadow" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/gshadow$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/gshadow$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Check if
    watch rule for /etc/gshadow already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/etc/gshadow\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_usergroup_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Use matched
    file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Add watch
    rule for /etc/gshadow in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Check if
    watch rule for /etc/gshadow already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/etc/gshadow\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_usergroup_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Use matched
    file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Add watch
    rule for /etc/gshadow in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Check if
    watch rule for /etc/gshadow already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/gshadow\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Add watch
    rule for /etc/gshadow in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Check if
    watch rule for /etc/gshadow already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/gshadow\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/gshadow - Add watch
    rule for /etc/gshadow in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/etc/gshadow -F perm=wa -F key=audit_rules_usergroup_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-87736-5
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_gshadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules gshadow 32  oval:ssg-test_audit_rules_usergroup_modification_gshadow_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_gshadow_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/gshadow\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules gshadow 64  oval:ssg-test_audit_rules_usergroup_modification_gshadow_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_gshadow_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/gshadow\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl gshadow 32  oval:ssg-test_audit_rules_usergroup_modification_gshadow_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_gshadow_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/gshadow\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl gshadow 64  oval:ssg-test_audit_rules_usergroup_modification_gshadow_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_gshadow_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/gshadow\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Events that Modify User/Group Information - /etc/security/opasswdxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_opasswd mediumCCE-90664-4

Record Events that Modify User/Group Information - /etc/security/opasswd

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_opasswd
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_usergroup_modification_opasswd:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90664-4

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nistAC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.5
os-srgSRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000503-CTR-001275
anssiR73
cis6.3.3.8
pcidss410.2.1.5, 10.2.1, 10.2
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification
Rationale
In addition to auditing new user and group accounts, these watches will alert the system administrator(s) to any modifications. Any unexpected users, groups, or modifications should be investigated for legitimacy.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/security/opasswd" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/security/opasswd$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/security/opasswd$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/security/opasswd" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/security/opasswd$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/security/opasswd$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/security/opasswd" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
    # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/security/opasswd" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/security/opasswd$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/security/opasswd$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/security/opasswd" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
    # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/security/opasswd" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/security/opasswd$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/security/opasswd$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Check if watch rule for /etc/security/opasswd already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/etc/security/opasswd\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
    for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Add watch rule for /etc/security/opasswd in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Check if watch rule for /etc/security/opasswd already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/etc/security/opasswd\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
    for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Use matched file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Add watch rule for /etc/security/opasswd in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Check if watch rule for /etc/security/opasswd already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/security/opasswd\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Add watch rule for /etc/security/opasswd in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Check if watch rule for /etc/security/opasswd already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/security/opasswd\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/security/opasswd -
    Add watch rule for /etc/security/opasswd in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/etc/security/opasswd -F perm=wa -F key=audit_rules_usergroup_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-90664-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_opasswd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules opasswd 32  oval:ssg-test_audit_rules_usergroup_modification_opasswd_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_opasswd_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/security\/opasswd\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules opasswd 64  oval:ssg-test_audit_rules_usergroup_modification_opasswd_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_opasswd_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/security\/opasswd\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl opasswd 32  oval:ssg-test_audit_rules_usergroup_modification_opasswd_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_opasswd_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/security\/opasswd\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl opasswd 64  oval:ssg-test_audit_rules_usergroup_modification_opasswd_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_opasswd_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/security\/opasswd\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Events that Modify User/Group Information - /etc/passwdxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_passwd mediumCCE-88286-0

Record Events that Modify User/Group Information - /etc/passwd

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_passwd
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_usergroup_modification_passwd:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88286-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nistAC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.5
os-srgSRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000304-GPOS-00121, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-OS-000274-GPOS-00104, SRG-OS-000275-GPOS-00105, SRG-OS-000276-GPOS-00106, SRG-OS-000277-GPOS-00107
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275
anssiR73
cis6.3.3.8
pcidss410.2.1.5, 10.2.1, 10.2
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification
Rationale
In addition to auditing new user and group accounts, these watches will alert the system administrator(s) to any modifications. Any unexpected users, groups, or modifications should be investigated for legitimacy.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/passwd" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/passwd$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/passwd$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification_passwd" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/passwd" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/passwd$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/passwd$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification_passwd" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/passwd" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules"
    # If the audit_rules_usergroup_modification_passwd.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/passwd" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/passwd$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/passwd$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification_passwd" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/passwd" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules"
    # If the audit_rules_usergroup_modification_passwd.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/passwd" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/passwd$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/passwd$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification_passwd" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Check if
    watch rule for /etc/passwd already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/etc/passwd\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_usergroup_modification_passwd
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification_passwd$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Use /etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Use matched
    file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Add watch
    rule for /etc/passwd in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification_passwd
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Check if
    watch rule for /etc/passwd already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/etc/passwd\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_usergroup_modification_passwd
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification_passwd$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Use /etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification_passwd.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Use matched
    file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Add watch
    rule for /etc/passwd in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification_passwd
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Check if
    watch rule for /etc/passwd already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/passwd\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Add watch
    rule for /etc/passwd in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification_passwd
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Check if
    watch rule for /etc/passwd already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/passwd\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/passwd - Add watch
    rule for /etc/passwd in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F key=audit_rules_usergroup_modification_passwd
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-88286-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules passwd 32  oval:ssg-test_audit_rules_usergroup_modification_passwd_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_passwd_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/passwd\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules passwd 64  oval:ssg-test_audit_rules_usergroup_modification_passwd_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_passwd_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/passwd\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl passwd 32  oval:ssg-test_audit_rules_usergroup_modification_passwd_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_passwd_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/passwd\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl passwd 64  oval:ssg-test_audit_rules_usergroup_modification_passwd_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_passwd_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/passwd\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Events that Modify User/Group Information - /etc/shadowxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_shadow mediumCCE-88637-4

Record Events that Modify User/Group Information - /etc/shadow

Rule IDxccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification_shadow
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_rules_usergroup_modification_shadow:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88637-4

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.1.7
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nistAC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
pcidssReq-10.2.5
os-srgSRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221
app-srg-ctrSRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275
anssiR73
cis6.3.3.8
pcidss410.2.1.5, 10.2.1, 10.2
Description
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification
-a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification
Rationale
In addition to auditing new user and group accounts, these watches will alert the system administrator(s) to any modifications. Any unexpected users, groups, or modifications should be investigated for legitimacy.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/shadow" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/shadow$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/shadow$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/shadow" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/shadow$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/shadow$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/shadow" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
    # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/shadow" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/shadow$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/etc/shadow$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/etc/shadow" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
    # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/etc/shadow" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/shadow$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/etc/shadow$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Check if
    watch rule for /etc/shadow already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/etc/shadow\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_usergroup_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Use matched
    file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Add watch
    rule for /etc/shadow in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Check if
    watch rule for /etc/shadow already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/etc/shadow\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Search /etc/audit/rules.d
    for other rules with specified key audit_rules_usergroup_modification
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/audit_rules_usergroup_modification.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Use matched
    file as the recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Add watch
    rule for /etc/shadow in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Check if
    watch rule for /etc/shadow already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/shadow\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Add watch
    rule for /etc/shadow in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Check if
    watch rule for /etc/shadow already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/etc/shadow\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Events that Modify User/Group Information - /etc/shadow - Add watch
    rule for /etc/shadow in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F key=audit_rules_usergroup_modification
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-88637-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.1.7
  - NIST-800-53-AC-2(4)
  - NIST-800-53-AC-6(9)
  - NIST-800-53-AU-12(c)
  - NIST-800-53-AU-2(d)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.5
  - audit_rules_usergroup_modification_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules shadow 32  oval:ssg-test_audit_rules_usergroup_modification_shadow_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_shadow_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/shadow\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules shadow 64  oval:ssg-test_audit_rules_usergroup_modification_shadow_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_shadow_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/shadow\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl shadow 32  oval:ssg-test_audit_rules_usergroup_modification_shadow_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_shadow_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/etc\/shadow\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl shadow 64  oval:ssg-test_audit_rules_usergroup_modification_shadow_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_usergroup_modification_shadow_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/etc\/shadow\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
Record Attempts to perform maintenance activitiesxccdf_org.ssgproject.content_rule_audit_sudo_log_events mediumCCE-89542-5

Record Attempts to perform maintenance activities

Rule IDxccdf_org.ssgproject.content_rule_audit_sudo_log_events
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-audit_sudo_log_events:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89542-5

References:
pcidssReq-10.2.2, Req-10.2.5.b
os-srgSRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215
anssiR73
cis6.3.3.3
pcidss410.2.1.3, 10.2.1, 10.2
Description
The Red Hat Enterprise Linux 10 operating system must generate audit records for privileged activities, nonlocal maintenance, diagnostic sessions and other system-level access. Verify the operating system audits activities performed during nonlocal maintenance and diagnostic sessions. Run the following command:
$ sudo auditctl -l | grep sudo.log
-w /var/log/sudo.log -p wa -k maintenance
If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following lines to a file with suffix .rules in the directory /etc/audit/rules.d:
-a always,exit -F arch=b32 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance
-a always,exit -F arch=b64 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance
If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following lines to /etc/audit/audit.rules:
-a always,exit -F arch=b32 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance
-a always,exit -F arch=b64 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance
Rationale
If events associated with nonlocal administrative access or diagnostic sessions are not logged, a major tool for assessing and investigating attacks would not be available. This requirement addresses auditing-related issues associated with maintenance tools used specifically for diagnostic and repair actions on organizational information systems. Nonlocal maintenance and diagnostic activities are those activities conducted by individuals communicating through a network, either an external network (e.g., the internet) or an internal network. Local maintenance and diagnostic activities are those activities carried out by individuals physically present at the information system or information system component and not communicating across a network connection. This requirement applies to hardware/software diagnostic test equipment or tools. This requirement does not cover hardware/software components that may support information system maintenance, yet are a part of the system, for example, the software implementing "ping," "ls," "ipconfig," or the hardware and software implementing the monitoring port of an Ethernet switch.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'






# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/sudo.log" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/sudo.log$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/sudo.log$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()


# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/sudo.log" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/sudo.log$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/sudo.log$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/maintenance.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/var/log/sudo.log" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/maintenance.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/maintenance.rules"
    # If the maintenance.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/sudo.log" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/sudo.log$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b32$sp\+-F$sp\+path=/var/log/sudo.log$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b32 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance" >> "$audit_rules_file"

    fi
done
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
# -----------------------------------------------------------------------------------------
#	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
# -----------------------------------------------------------------------------------------
# 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
# 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
# -----------------------------------------------------------------------------------------
files_to_inspect=()

# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/maintenance.rules' to list of files for inspection.

readarray -t matches < <(grep -HP "[\s]*-F[\s]+path=/var/log/sudo.log" /etc/audit/rules.d/*.rules)


# For each of the matched entries
for match in "${matches[@]}"
do
    # Extract filepath from the match
    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    # Append that path into list of files for inspection
    files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
    # Append '/etc/audit/rules.d/maintenance.rules' into list of files for inspection
    key_rule_file="/etc/audit/rules.d/maintenance.rules"
    # If the maintenance.rules file doesn't exist yet, create it with correct permissions
    if [ ! -e "$key_rule_file" ]
    then
        touch "$key_rule_file"
        chmod 0600 "$key_rule_file"
    fi
    files_to_inspect+=("$key_rule_file")
fi

# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
    # Check if audit watch file system object rule for given path already present

    if grep -q -P -- "^[\s]*-F[\s]+path=/var/log/sudo.log" "$audit_rules_file"

    then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Define BRE whitespace class shortcut
        sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule

        current_access_bits=$(sed -ne "s#$sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/sudo.log$sp\+-F$sp\+perm=\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")

        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo "wa" | grep -o .)
        do
            # For each from the required access bits (e.g. 'w', 'a') check
            # if they are already present in current access bits for rule.
            # If not, append that bit at the end
            if ! grep -q "$access_bit" <<< "$current_access_bits"
            then
                # Concatenate the existing mask with the missing bit
                current_access_bits="$current_access_bits$access_bit"
            fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule

        sed -i "s#\($sp*-a$sp\+always,exit$sp\+-F$sp\+arch=b64$sp\+-F$sp\+path=/var/log/sudo.log$sp\+-F$sp\+perm=\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"

    else
        # Rule isn't present yet. Append it at the end of $audit_rules_file file
        # with proper key


        echo "-a always,exit -F arch=b64 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance" >> "$audit_rules_file"

    fi
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Check if watch rule for
    /var/log/sudo.log already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b32\s+-F\s+path=/var/log/sudo.log\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Search /etc/audit/rules.d
    for other rules with specified key maintenance
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)maintenance$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Use /etc/audit/rules.d/maintenance.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/maintenance.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Use matched file as the
    recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Add watch rule for /var/log/sudo.log
    in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b32 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Check if watch rule for
    /var/log/sudo.log already exists in /etc/audit/rules.d/
  find:
    paths: /etc/audit/rules.d
    contains: ^\s*-a\s+always,exit\s+-F\s+arch=b64\s+-F\s+path=/var/log/sudo.log\s+-F\s+perm=wa(\s|$)+
    patterns: '*.rules'
  register: find_existing_watch_rules_d
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Search /etc/audit/rules.d
    for other rules with specified key maintenance
  find:
    paths: /etc/audit/rules.d
    contains: ^.*(?:-F key=|-k\s+)maintenance$
    patterns: '*.rules'
  register: find_watch_key
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Use /etc/audit/rules.d/maintenance.rules
    as the recipient for the rule
  set_fact:
    all_files:
    - /etc/audit/rules.d/maintenance.rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Use matched file as the
    recipient for the rule
  set_fact:
    all_files:
    - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
    is defined and find_existing_watch_rules_d.matched == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Add watch rule for /var/log/sudo.log
    in /etc/audit/rules.d/
  lineinfile:
    path: '{{ all_files[0] }}'
    line: -a always,exit -F arch=b64 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
    == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Check if watch rule for
    /var/log/sudo.log already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/var/log/sudo.log\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Add watch rule for /var/log/sudo.log
    in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b32 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Check if watch rule for
    /var/log/sudo.log already exists in /etc/audit/audit.rules
  find:
    paths: /etc/audit/
    contains: ^\s*-F\s+path=/var/log/sudo.log\s+-F\s+perms=wa(\s|$)+
    patterns: audit.rules
  register: find_existing_watch_audit_rules
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Record Attempts to perform maintenance activities - Add watch rule for /var/log/sudo.log
    in /etc/audit/audit.rules
  lineinfile:
    line: -a always,exit -F arch=b64 -F path=/var/log/sudo.log -F perm=wa -F key=maintenance
    state: present
    dest: /etc/audit/audit.rules
    create: true
    mode: '0600'
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
    == 0
  tags:
  - CCE-89542-5
  - PCI-DSS-Req-10.2.2
  - PCI-DSS-Req-10.2.5.b
  - PCI-DSSv4-10.2
  - PCI-DSSv4-10.2.1
  - PCI-DSSv4-10.2.1.3
  - audit_sudo_log_events
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
OVAL test results details

audit augenrules  oval:ssg-test_audit_rules_augenrules:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/usr/lib/systemd/system/audit-rules.serviceExecStart=/usr/sbin/augenrules --load

audit augenrules sudo_log 32  oval:ssg-test_audit_sudo_log_events_augenrules_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_sudo_log_events_augenrules_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/var\/log\/sudo.log\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit augenrules sudo_log 64  oval:ssg-test_audit_sudo_log_events_augenrules_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_sudo_log_events_augenrules_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/var\/log\/sudo.log\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$^/etc/audit/rules\.d/.*\.rules$1

audit auditctl  oval:ssg-test_audit_rules_auditctl:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_rules_auditctl:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/usr/lib/systemd/system/audit-rules.service^ExecStart=\/sbin\/auditctl.*$1

audit auditctl sudo_log 32  oval:ssg-test_audit_sudo_log_events_auditctl_32:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_sudo_log_events_auditctl_32:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b32\s+\-F\s+path=\/var\/log\/sudo.log\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1

audit auditctl sudo_log 64  oval:ssg-test_audit_sudo_log_events_auditctl_64:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_sudo_log_events_auditctl_64:obj:1 of type textfilecontent54_object
FilepathPatternInstance
^\-a\s+always,exit\s+\-F\s+arch=b64\s+\-F\s+path=\/var\/log\/sudo.log\s+\-F\s+perm=\b([rx]*w[rx]*a[rx]*|[rx]*a[rx]*w[rx]*)\b.*$/etc/audit/audit.rules1
System Audit Logs Must Have Mode 0750 or Less Permissivexccdf_org.ssgproject.content_rule_directory_permissions_var_log_audit mediumCCE-86750-7

System Audit Logs Must Have Mode 0750 or Less Permissive

Rule IDxccdf_org.ssgproject.content_rule_directory_permissions_var_log_audit
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-directory_permissions_var_log_audit:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-86750-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8
cobit5APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.2, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-004-6 R3.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CIP-007-3 R6.5
nistCM-6(a), AC-6(1), AU-9
nist-csfDE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4
os-srgSRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029
cis6.3.4.1
Description
If log_group in /etc/audit/auditd.conf is set to a group other than the root group account, change the mode of the audit log files with the following command:
$ sudo chmod 0750 /var/log/audit

Otherwise, change the mode of the audit log files with the following command:
$ sudo chmod 0700 /var/log/audit
Rationale
If users can write to audit logs, audit trails can be modified or destroyed.
OVAL test results details

log_file not set  oval:ssg-test_auditd_conf_log_file_not_set:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_file = /var/log/audit/audit.log

/var/log/audit mode 0700  oval:ssg-test_dir_permissions_audit_log:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_log_directory:obj:1 of type file_object
BehaviorsPathFilenameFilter
/var/log/audit/audit.log
/var/log/audit
no valueno valueoval:ssg-state_not_mode_0700:ste:1

log_file not set  oval:ssg-test_auditd_conf_log_file_not_set:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_file = /var/log/audit/audit.log

/var/log/audit mode 0700  oval:ssg-test_dir_permissions_var_log_audit:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_var_log_audit_directory:obj:1 of type file_object
BehaviorsPathFilenameFilter
no value/var/log/auditno valueoval:ssg-state_not_mode_0700:ste:1
System Audit Logs Must Be Group Owned By Rootxccdf_org.ssgproject.content_rule_file_group_ownership_var_log_audit mediumCCE-89126-7

System Audit Logs Must Be Group Owned By Root

Rule IDxccdf_org.ssgproject.content_rule_file_group_ownership_var_log_audit
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_group_ownership_var_log_audit:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89126-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01
cui3.3.1
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1), AU-9(4)
nist-csfDE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4
pcidssReq-10.5.1
os-srgSRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029, SRG-OS-000206-GPOS-00084
cis6.3.4.4
pcidss410.3.2, 10.3
Description
All audit logs must be group owned by root user. The path for audit log can be configured via log_file parameter in
/etc/audit/auditd.conf
or, by default, the path for audit log is
/var/log/audit/
. To properly set the group owner of /var/log/audit/*, run the command:
$ sudo chgrp root /var/log/audit/*
If log_group in /etc/audit/auditd.conf is set to a group other than the root group account, change the group ownership of the audit logs to this specific group.
Rationale
Unauthorized disclosure of audit records can reveal system and configuration data to attackers, thus compromising its confidentiality.
OVAL test results details

log_file not set  oval:ssg-test_auditd_conf_log_file_not_set:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_file = /var/log/audit/audit.log

audit log files gid root  oval:ssg-test_group_ownership_audit_log_files:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_group_ownership_audit_log_files:obj:1 of type file_object
FilepathFilter
/var/log/audit/audit.logoval:ssg-state_group_owner_not_root_var_log_audit:ste:1

log_group = root  oval:ssg-test_auditd_conf_log_group_not_root:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_group = root

log_group is set  oval:ssg-test_auditd_conf_log_group_is_set:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_group = root

log_file not set  oval:ssg-test_auditd_conf_log_file_not_set:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_file = /var/log/audit/audit.log

audit log files gid root  oval:ssg-test_group_ownership_default_audit_log_files:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_group_ownership_default_audit_log_files:obj:1 of type file_object
FilepathFilter
/var/log/audit/audit.logoval:ssg-state_group_owner_not_root_var_log_audit:ste:1

log_group = root  oval:ssg-test_auditd_conf_log_group_not_root:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_group = root

log_group is set  oval:ssg-test_auditd_conf_log_group_is_set:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_group = root
Audit Configuration Files Must Be Owned By Group rootxccdf_org.ssgproject.content_rule_file_groupownership_audit_configuration mediumCCE-88238-1

Audit Configuration Files Must Be Owned By Group root

Rule IDxccdf_org.ssgproject.content_rule_file_groupownership_audit_configuration
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupownership_audit_configuration:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88238-1

References:
os-srgSRG-OS-000063-GPOS-00032
cis6.3.4.7
Description
All audit configuration files must be owned by group root.
chown :root /etc/audit/audit*.{rules,conf} /etc/audit/rules.d/*
Rationale
Without the capability to restrict which roles and individuals can select which events are audited, unauthorized personnel may be able to prevent the auditing of critical events. Misconfigured audits may degrade the system's performance by overwhelming the audit log. Misconfigured audits may also make it more difficult to establish, correlate, and investigate the events relating to an incident or identify those responsible for one.
OVAL test results details

Testing group ownership of /etc/audit/  oval:ssg-test_file_groupownership_audit_configuration_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_audit_configuration_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/audit^.*audit(\.rules|d\.conf)$oval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_audit_configuration_0_0:ste:1

Testing group ownership of /etc/audit/rules.d/  oval:ssg-test_file_groupownership_audit_configuration_1:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_audit_configuration_1:obj:1 of type file_object
PathFilenameFilterFilter
/etc/audit/rules.d^.*\.rules$oval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_audit_configuration_0_0:ste:1
Audit Configuration Files Must Be Owned By Rootxccdf_org.ssgproject.content_rule_file_ownership_audit_configuration mediumCCE-88877-6

Audit Configuration Files Must Be Owned By Root

Rule IDxccdf_org.ssgproject.content_rule_file_ownership_audit_configuration
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_ownership_audit_configuration:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88877-6

References:
os-srgSRG-OS-000063-GPOS-00032
cis6.3.4.6
Description
All audit configuration files must be owned by root user. To properly set the owner of /etc/audit/, run the command:
$ sudo chown root /etc/audit/ 
To properly set the owner of /etc/audit/rules.d/, run the command:
$ sudo chown root /etc/audit/rules.d/ 
Rationale
Without the capability to restrict which roles and individuals can select which events are audited, unauthorized personnel may be able to prevent the auditing of critical events. Misconfigured audits may degrade the system's performance by overwhelming the audit log. Misconfigured audits may also make it more difficult to establish, correlate, and investigate the events relating to an incident or identify those responsible for one.
OVAL test results details

Testing user ownership of /etc/audit/  oval:ssg-test_file_ownership_audit_configuration_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_audit_configuration_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/audit^.*audit(\.rules|d\.conf)$oval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_audit_configuration_0_0:ste:1

Testing user ownership of /etc/audit/rules.d/  oval:ssg-test_file_ownership_audit_configuration_1:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_audit_configuration_1:obj:1 of type file_object
PathFilenameFilterFilter
/etc/audit/rules.d^.*\.rules$oval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_audit_configuration_0_0:ste:1
System Audit Logs Must Be Owned By Rootxccdf_org.ssgproject.content_rule_file_ownership_var_log_audit_stig mediumCCE-89939-3

System Audit Logs Must Be Owned By Root

Rule IDxccdf_org.ssgproject.content_rule_file_ownership_var_log_audit_stig
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_ownership_var_log_audit_stig:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-89939-3

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01
cui3.3.1
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1), AU-9(4)
nist-csfDE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4
pcidssReq-10.5.1
os-srgSRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029, SRG-OS-000206-GPOS-00084
cis6.3.4.3
Description
All audit logs must be owned by root user. The path for audit log can be configured via log_file parameter in
/etc/audit/auditd.conf
or by default, the path for audit log is
/var/log/audit/
. To properly set the owner of /var/log/audit/*, run the command:
$ sudo chown root /var/log/audit/* 
Rationale
Unauthorized disclosure of audit records can reveal system and configuration data to attackers, thus compromising its confidentiality.
OVAL test results details

log_file not set  oval:ssg-test_auditd_conf_log_file_not_set:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_file = /var/log/audit/audit.log

audit log files uid root  oval:ssg-test_user_ownership_audit_log_files:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_user_ownership_audit_log_files:obj:1 of type file_object
FilepathFilter
/var/log/audit/audit.logoval:ssg-state_owner_not_root_var_log_audit:ste:1

log_file not set  oval:ssg-test_auditd_conf_log_file_not_set:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_file = /var/log/audit/audit.log

var/log/audit/audit.log file uid root  oval:ssg-test_user_ownership_audit_default_log_files:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_user_ownership_audit_default_log_files:obj:1 of type file_object
FilepathFilter
/var/log/audit/audit.logoval:ssg-state_owner_not_root_var_log_audit:ste:1
Audit Configuration Files Permissions are 640 or More Restrictivexccdf_org.ssgproject.content_rule_file_permissions_audit_configuration mediumCCE-88067-4

Audit Configuration Files Permissions are 640 or More Restrictive

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_audit_configuration
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_audit_configuration:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-88067-4

References:
nistAU-12 b
os-srgSRG-OS-000063-GPOS-00032
cis6.3.4.5
Description
All audit configuration files permissions must be 640 or more restrictive.
chmod 0640 /etc/audit/audit*.{rules,conf} /etc/audit/rules.d/*
Rationale
Without the capability to restrict which roles and individuals can select which events are audited, unauthorized personnel may be able to prevent the auditing of critical events. Misconfigured audits may degrade the system's performance by overwhelming the audit log. Misconfigured audits may also make it more difficult to establish, correlate, and investigate the events relating to an incident or identify those responsible for one.
OVAL test results details

Testing mode of /etc/audit/  oval:ssg-test_file_permissions_audit_configuration_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_audit_configuration_0:obj:1 of type file_object
PathFilenameFilterFilter
/etc/audit^.*audit(\.rules|d\.conf)$oval:ssg-exclude_symlinks__audit_configuration:ste:1oval:ssg-state_file_permissions_audit_configuration_0_mode_0640or_stricter_:ste:1

Testing mode of /etc/audit/rules.d/  oval:ssg-test_file_permissions_audit_configuration_1:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_audit_configuration_1:obj:1 of type file_object
PathFilenameFilterFilter
/etc/audit/rules.d^.*\.rules$oval:ssg-exclude_symlinks__audit_configuration:ste:1oval:ssg-state_file_permissions_audit_configuration_1_mode_0640or_stricter_:ste:1
System Audit Logs Must Have Mode 0640 or Less Permissivexccdf_org.ssgproject.content_rule_file_permissions_var_log_audit mediumCCE-90129-8

System Audit Logs Must Have Mode 0640 or Less Permissive

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_var_log_audit
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_var_log_audit:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-90129-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01
cui3.3.1
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1), AU-9(4)
nist-csfDE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4
pcidssReq-10.5
os-srgSRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029, SRG-OS-000206-GPOS-00084
app-srg-ctrSRG-APP-000118-CTR-000240
cis6.3.4.2
pcidss410.3.1, 10.3
Description
Determine where the audit logs are stored with the following command:
$ sudo grep -iw log_file /etc/audit/auditd.conf
log_file = /var/log/audit/audit.log
Configure the audit log to be protected from unauthorized read access by setting the correct permissive mode with the following command:
$ sudo chmod 0600 audit_log_file
       
By default, audit_log_file is "/var/log/audit/audit.log".
Rationale
If users can write to audit logs, audit trails can be modified or destroyed.
OVAL test results details

log_file not set  oval:ssg-test_auditd_conf_log_file_not_set:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_file = /var/log/audit/audit.log

audit log files mode 0600  oval:ssg-test_file_permissions_audit_log:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_log_files:obj:1 of type file_object
FilepathFilter
/var/log/audit/audit.logoval:ssg-state_not_mode_0600:ste:1

log_file not set  oval:ssg-test_auditd_conf_log_file_not_set:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
not evaluated/etc/audit/auditd.conflog_file = /var/log/audit/audit.log

default audit log files mode 0600  oval:ssg-test_file_permissions_default_audit_log:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_audit_default_log_files:obj:1 of type file_object
FilepathFilter
/var/log/audit/audit.logoval:ssg-state_not_mode_0600:ste:1
Configure auditd Disk Error Action on Disk Errorxccdf_org.ssgproject.content_rule_auditd_data_disk_error_action mediumCCE-87470-1

Configure auditd Disk Error Action on Disk Error

Rule IDxccdf_org.ssgproject.content_rule_auditd_data_disk_error_action
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-auditd_data_disk_error_action:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-87470-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8
cobit5APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2
iso27001-2013A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1
nistAU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4
os-srgSRG-OS-000047-GPOS-00023
app-srg-ctrSRG-APP-000098-CTR-000185, SRG-APP-000099-CTR-000190, SRG-APP-000100-CTR-000195, SRG-APP-000100-CTR-000200, SRG-APP-000109-CTR-000215, SRG-APP-000290-CTR-000670, SRG-APP-000357-CTR-000800
cis6.3.2.3
Description
The auditd service can be configured to take an action when there is a disk error. Edit the file /etc/audit/auditd.conf. Add or modify the following line, substituting ACTION appropriately:
disk_error_action = ACTION
       
Set this value to single to cause the system to switch to single-user mode for corrective action. Acceptable values also include syslog, exec, single, and halt For certain systems, the need for availability outweighs the need to log all actions, and a different setting should be determined. Details regarding all possible values for ACTION are described in the auditd.conf man page.
Rationale
Taking appropriate action in case of disk errors will minimize the possibility of losing audit records.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

var_auditd_disk_error_action='syslog|single|halt'


#
# If disk_error_action present in /etc/audit/auditd.conf, change value
# to var_auditd_disk_error_action, else
# add "disk_error_action = $var_auditd_disk_error_action" to /etc/audit/auditd.conf
#
var_auditd_disk_error_action="$(echo $var_auditd_disk_error_action | cut -d \| -f 1)"

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^disk_error_action")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_disk_error_action"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^disk_error_action\\>" "/etc/audit/auditd.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^disk_error_action\\>.*/$escaped_formatted_output/gi" "/etc/audit/auditd.conf"
else
    if [[ -s "/etc/audit/auditd.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/audit/auditd.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/audit/auditd.conf"
    fi
    cce="CCE-87470-1"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/audit/auditd.conf" >> "/etc/audit/auditd.conf"
    printf '%s\n' "$formatted_output" >> "/etc/audit/auditd.conf"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-87470-1
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - auditd_data_disk_error_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_auditd_disk_error_action # promote to variable
  set_fact:
    var_auditd_disk_error_action: !!str syslog|single|halt
  tags:
    - always

- name: Configure auditd Disk Error Action on Disk Error
  lineinfile:
    dest: /etc/audit/auditd.conf
    line: disk_error_action = {{ var_auditd_disk_error_action.split('|')[0] }}
    regexp: ^\s*disk_error_action\s*=\s*.*$
    state: present
    create: true
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-87470-1
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - auditd_data_disk_error_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,{{ %23%0A%23%20This%20file%20controls%20the%20configuration%20of%20the%20audit%20daemon%0A%23%0A%0Alocal_events%20%3D%20yes%0Awrite_logs%20%3D%20yes%0Alog_file%20%3D%20/var/log/audit/audit.log%0Alog_group%20%3D%20root%0Alog_format%20%3D%20ENRICHED%0Aflush%20%3D%20%7B%7B.var_auditd_flush%7D%7D%0Afreq%20%3D%2050%0Amax_log_file%20%3D%20%7B%7B.var_auditd_max_log_file%7D%7D%0Anum_logs%20%3D%20%7B%7B.var_auditd_num_logs%7D%7D%0Apriority_boost%20%3D%204%0Aname_format%20%3D%20hostname%0A%23%23name%20%3D%20mydomain%0Amax_log_file_action%20%3D%20%7B%7B.var_auditd_max_log_file_action%7D%7D%0Aspace_left%20%3D%20%7B%7B.var_auditd_space_left%7D%7D%0Aspace_left_action%20%3D%20%7B%7B.var_auditd_space_left_action%7D%7D%0Averify_email%20%3D%20yes%0Aaction_mail_acct%20%3D%20%7B%7B.var_auditd_action_mail_acct%7D%7D%0Aadmin_space_left%20%3D%2050%0Aadmin_space_left_action%20%3D%20syslog%0Adisk_full_action%20%3D%20%7B%7B.var_auditd_disk_full_action%7D%7D%0Adisk_error_action%20%3D%20%7B%7B.var_auditd_disk_error_action%7D%7D%0Ause_libwrap%20%3D%20yes%0A%23%23tcp_listen_port%20%3D%2060%0Atcp_listen_queue%20%3D%205%0Atcp_max_per_addr%20%3D%201%0A%23%23tcp_client_ports%20%3D%201024-65535%0Atcp_client_max_idle%20%3D%200%0Atransport%20%3D%20TCP%0Akrb5_principal%20%3D%20auditd%0A%23%23krb5_key_file%20%3D%20/etc/audit/audit.key%0Adistribute_network%20%3D%20no%0Aq_depth%20%3D%20400%0Aoverflow_action%20%3D%20syslog%0Amax_restarts%20%3D%2010%0Aplugin_dir%20%3D%20/etc/audit/plugins.d }}
        mode: 0640
        path: /etc/audit/auditd.conf
        overwrite: true
OVAL test results details

disk full action  oval:ssg-test_auditd_data_disk_error_action:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/audit/auditd.confdisk_error_action = SUSPEND
Configure auditd Disk Full Action when Disk Space Is Fullxccdf_org.ssgproject.content_rule_auditd_data_disk_full_action mediumCCE-88198-7

Configure auditd Disk Full Action when Disk Space Is Full

Rule IDxccdf_org.ssgproject.content_rule_auditd_data_disk_full_action
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-auditd_data_disk_full_action:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-88198-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8
cobit5APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2
iso27001-2013A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1
nistAU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4
os-srgSRG-OS-000047-GPOS-00023
cis6.3.2.3
Description
The auditd service can be configured to take an action when disk space is running low but prior to running out of space completely. Edit the file /etc/audit/auditd.conf. Add or modify the following line, substituting ACTION appropriately:
disk_full_action = ACTION
       
Set this value to single to cause the system to switch to single-user mode for corrective action. Acceptable values also include syslog, exec, single, and halt For certain systems, the need for availability outweighs the need to log all actions, and a different setting should be determined. Details regarding all possible values for ACTION are described in the auditd.conf man page.
Rationale
Taking appropriate action in case of a filled audit storage volume will minimize the possibility of losing audit records.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

var_auditd_disk_full_action='syslog|single|halt'


var_auditd_disk_full_action="$(echo $var_auditd_disk_full_action | cut -d \| -f 1)"

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^disk_full_action")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_disk_full_action"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^disk_full_action\\>" "/etc/audit/auditd.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^disk_full_action\\>.*/$escaped_formatted_output/gi" "/etc/audit/auditd.conf"
else
    if [[ -s "/etc/audit/auditd.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/audit/auditd.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/audit/auditd.conf"
    fi
    cce="CCE-88198-7"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/audit/auditd.conf" >> "/etc/audit/auditd.conf"
    printf '%s\n' "$formatted_output" >> "/etc/audit/auditd.conf"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88198-7
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - auditd_data_disk_full_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_auditd_disk_full_action # promote to variable
  set_fact:
    var_auditd_disk_full_action: !!str syslog|single|halt
  tags:
    - always

- name: Configure auditd Disk Full Action when Disk Space Is Full
  lineinfile:
    dest: /etc/audit/auditd.conf
    line: disk_full_action = {{ var_auditd_disk_full_action.split('|')[0] }}
    regexp: ^\s*disk_full_action\s*=\s*.*$
    state: present
    create: true
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88198-7
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - auditd_data_disk_full_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,{{ %23%0A%23%20This%20file%20controls%20the%20configuration%20of%20the%20audit%20daemon%0A%23%0A%0Alocal_events%20%3D%20yes%0Awrite_logs%20%3D%20yes%0Alog_file%20%3D%20/var/log/audit/audit.log%0Alog_group%20%3D%20root%0Alog_format%20%3D%20ENRICHED%0Aflush%20%3D%20%7B%7B.var_auditd_flush%7D%7D%0Afreq%20%3D%2050%0Amax_log_file%20%3D%20%7B%7B.var_auditd_max_log_file%7D%7D%0Anum_logs%20%3D%20%7B%7B.var_auditd_num_logs%7D%7D%0Apriority_boost%20%3D%204%0Aname_format%20%3D%20hostname%0A%23%23name%20%3D%20mydomain%0Amax_log_file_action%20%3D%20%7B%7B.var_auditd_max_log_file_action%7D%7D%0Aspace_left%20%3D%20%7B%7B.var_auditd_space_left%7D%7D%0Aspace_left_action%20%3D%20%7B%7B.var_auditd_space_left_action%7D%7D%0Averify_email%20%3D%20yes%0Aaction_mail_acct%20%3D%20%7B%7B.var_auditd_action_mail_acct%7D%7D%0Aadmin_space_left%20%3D%2050%0Aadmin_space_left_action%20%3D%20syslog%0Adisk_full_action%20%3D%20%7B%7B.var_auditd_disk_full_action%7D%7D%0Adisk_error_action%20%3D%20%7B%7B.var_auditd_disk_error_action%7D%7D%0Ause_libwrap%20%3D%20yes%0A%23%23tcp_listen_port%20%3D%2060%0Atcp_listen_queue%20%3D%205%0Atcp_max_per_addr%20%3D%201%0A%23%23tcp_client_ports%20%3D%201024-65535%0Atcp_client_max_idle%20%3D%200%0Atransport%20%3D%20TCP%0Akrb5_principal%20%3D%20auditd%0A%23%23krb5_key_file%20%3D%20/etc/audit/audit.key%0Adistribute_network%20%3D%20no%0Aq_depth%20%3D%20400%0Aoverflow_action%20%3D%20syslog%0Amax_restarts%20%3D%2010%0Aplugin_dir%20%3D%20/etc/audit/plugins.d }}
        mode: 0640
        path: /etc/audit/auditd.conf
        overwrite: true
OVAL test results details

disk error action  oval:ssg-test_auditd_data_disk_full_action:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/audit/auditd.confdisk_full_action = SUSPEND
Configure auditd mail_acct Action on Low Disk Spacexccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct mediumCCE-89081-4

Configure auditd mail_acct Action on Low Disk Space

Rule IDxccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-auditd_data_retention_action_mail_acct:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-89081-4

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01
cui3.3.1
hipaa164.312(a)(2)(ii)
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2
iso27001-2013A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1
nerc-cipCIP-003-8 R1.3, CIP-003-8 R3, CIP-003-8 R3.1, CIP-003-8 R3.2, CIP-003-8 R3.3, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistIA-5(1), AU-5(a), AU-5(2), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4
pcidssReq-10.7.a
os-srgSRG-OS-000046-GPOS-00022, SRG-OS-000343-GPOS-00134
cis6.3.2.4
Description
The auditd service can be configured to send email to a designated account in certain situations. Add or correct the following line in /etc/audit/auditd.conf to ensure that administrators are notified via email for those situations:
action_mail_acct = root
       
Rationale
Email sent to the root account is typically aliased to the administrators of the system, who can take appropriate action.
OVAL test results details

email account for actions  oval:ssg-test_auditd_data_retention_action_mail_acct:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/audit/auditd.confaction_mail_acct = root
Configure auditd admin_space_left Action on Low Disk Spacexccdf_org.ssgproject.content_rule_auditd_data_retention_admin_space_left_action mediumCCE-89040-0

Configure auditd admin_space_left Action on Low Disk Space

Rule IDxccdf_org.ssgproject.content_rule_auditd_data_retention_admin_space_left_action
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-auditd_data_retention_admin_space_left_action:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-89040-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01
cui3.3.1
hipaa164.312(a)(2)(ii)
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2
iso27001-2013A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1
nistAU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4
pcidssReq-10.7
os-srgSRG-OS-000343-GPOS-00134
cis6.3.2.4
pcidss410.5.1, 10.5
Description
The auditd service can be configured to take an action when disk space is running low but prior to running out of space completely. Edit the file /etc/audit/auditd.conf. Add or modify the following line, substituting ACTION appropriately:
admin_space_left_action = ACTION
       
Set this value to single to cause the system to switch to single user mode for corrective action. Acceptable values also include suspend and halt. For certain systems, the need for availability outweighs the need to log all actions, and a different setting should be determined. Details regarding all possible values for ACTION are described in the auditd.conf man page.
Rationale
Administrators should be made aware of an inability to record audit records. If a separate partition or logical volume of adequate size is used, running low on space for audit records should never occur.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

var_auditd_admin_space_left_action='single|halt'


var_auditd_admin_space_left_action="$(echo $var_auditd_admin_space_left_action | cut -d \| -f 1)"

AUDITCONFIG=/etc/audit/auditd.conf

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^admin_space_left_action")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_admin_space_left_action"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^admin_space_left_action\\>" "$AUDITCONFIG"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^admin_space_left_action\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG"
else
    if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG"
    fi
    cce="CCE-89040-0"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG"
    printf '%s\n' "$formatted_output" >> "$AUDITCONFIG"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-89040-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - PCI-DSSv4-10.5
  - PCI-DSSv4-10.5.1
  - auditd_data_retention_admin_space_left_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_auditd_admin_space_left_action # promote to variable
  set_fact:
    var_auditd_admin_space_left_action: !!str single|halt
  tags:
    - always

- name: Configure auditd admin_space_left Action on Low Disk Space
  lineinfile:
    dest: /etc/audit/auditd.conf
    line: admin_space_left_action = {{ var_auditd_admin_space_left_action .split('|')[0]
      }}
    regexp: ^\s*admin_space_left_action\s*=\s*.*$
    state: present
    create: true
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-89040-0
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - PCI-DSSv4-10.5
  - PCI-DSSv4-10.5.1
  - auditd_data_retention_admin_space_left_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,{{ %23%0A%23%20This%20file%20controls%20the%20configuration%20of%20the%20audit%20daemon%0A%23%0A%0Alocal_events%20%3D%20yes%0Awrite_logs%20%3D%20yes%0Alog_file%20%3D%20/var/log/audit/audit.log%0Alog_group%20%3D%20root%0Alog_format%20%3D%20ENRICHED%0Aflush%20%3D%20%7B%7B.var_auditd_flush%7D%7D%0Afreq%20%3D%2050%0Amax_log_file%20%3D%20%7B%7B.var_auditd_max_log_file%7D%7D%0Anum_logs%20%3D%20%7B%7B.var_auditd_num_logs%7D%7D%0Apriority_boost%20%3D%204%0Aname_format%20%3D%20hostname%0A%23%23name%20%3D%20mydomain%0Amax_log_file_action%20%3D%20%7B%7B.var_auditd_max_log_file_action%7D%7D%0Aspace_left%20%3D%20%7B%7B.var_auditd_space_left%7D%7D%0Aspace_left_action%20%3D%20%7B%7B.var_auditd_space_left_action%7D%7D%0Averify_email%20%3D%20yes%0Aaction_mail_acct%20%3D%20%7B%7B.var_auditd_action_mail_acct%7D%7D%0Aadmin_space_left%20%3D%2050%0Aadmin_space_left_action%20%3D%20syslog%0Adisk_full_action%20%3D%20%7B%7B.var_auditd_disk_full_action%7D%7D%0Adisk_error_action%20%3D%20%7B%7B.var_auditd_disk_error_action%7D%7D%0Ause_libwrap%20%3D%20yes%0A%23%23tcp_listen_port%20%3D%2060%0Atcp_listen_queue%20%3D%205%0Atcp_max_per_addr%20%3D%201%0A%23%23tcp_client_ports%20%3D%201024-65535%0Atcp_client_max_idle%20%3D%200%0Atransport%20%3D%20TCP%0Akrb5_principal%20%3D%20auditd%0A%23%23krb5_key_file%20%3D%20/etc/audit/audit.key%0Adistribute_network%20%3D%20no%0Aq_depth%20%3D%20400%0Aoverflow_action%20%3D%20syslog%0Amax_restarts%20%3D%2010%0Aplugin_dir%20%3D%20/etc/audit/plugins.d }}
        mode: 0640
        path: /etc/audit/auditd.conf
        overwrite: true
OVAL test results details

space left action  oval:ssg-test_auditd_data_retention_admin_space_left_action:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/audit/auditd.confadmin_space_left_action = SUSPEND
Configure auditd Max Log File Sizexccdf_org.ssgproject.content_rule_auditd_data_retention_max_log_file mediumCCE-89263-8

Configure auditd Max Log File Size

Rule IDxccdf_org.ssgproject.content_rule_auditd_data_retention_max_log_file
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-auditd_data_retention_max_log_file:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-89263-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1
iso27001-2013A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7
nerc-cipCIP-004-6 R2.2.3, CIP-004-6 R3.3, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, CIP-007-3 R6.5
nistAU-11, CM-6(a)
nist-csfDE.AE-3, DE.AE-5, PR.PT-1, RS.AN-1, RS.AN-4
pcidssReq-10.7
cis6.3.2.1
Description
Determine the amount of audit data (in megabytes) which should be retained in each log file. Edit the file /etc/audit/auditd.conf. Add or modify the following line, substituting the correct value of 6 for STOREMB:
max_log_file = STOREMB
       
Set the value to 6 (MB) or higher for general-purpose systems. Larger values, of course, support retention of even more audit data.
Rationale
The total storage for audit log files must be large enough to retain log information over the period required. This is a function of the maximum log file size and the number of logs retained.
OVAL test results details

max log file size  oval:ssg-test_auditd_data_retention_max_log_file:tst:1  true

Following items have been found on the system:
Result of item-state comparisonPathContent
true/etc/audit/auditd.confmax_log_file = 8
Configure auditd max_log_file_action Upon Reaching Maximum Log Sizexccdf_org.ssgproject.content_rule_auditd_data_retention_max_log_file_action mediumCCE-86674-9

Configure auditd max_log_file_action Upon Reaching Maximum Log Size

Rule IDxccdf_org.ssgproject.content_rule_auditd_data_retention_max_log_file_action
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-auditd_data_retention_max_log_file_action:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-86674-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01
hipaa164.312(a)(2)(ii)
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2
iso27001-2013A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1
nistAU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4
pcidssReq-10.7
os-srgSRG-OS-000047-GPOS-00023
cis6.3.2.2
Description
The default action to take when the logs reach their maximum size is to rotate the log files, discarding the oldest one. To configure the action taken by auditd, add or correct the line in /etc/audit/auditd.conf:
max_log_file_action = ACTION
       
Possible values for ACTION are described in the auditd.conf man page. These include:
  • ignore
  • syslog
  • suspend
  • rotate
  • keep_logs
Set the ACTION to keep_logs. The setting is case-insensitive.
Rationale
Automatically rotating logs (by setting this to rotate) minimizes the chances of the system unexpectedly running out of disk space by being overwhelmed with log data. However, for systems that must never discard log data, or which use external processes to transfer it and reclaim space, keep_logs can be employed.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

var_auditd_max_log_file_action='keep_logs'


AUDITCONFIG=/etc/audit/auditd.conf

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^max_log_file_action")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_max_log_file_action"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^max_log_file_action\\>" "$AUDITCONFIG"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^max_log_file_action\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG"
else
    if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG"
    fi
    cce="CCE-86674-9"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG"
    printf '%s\n' "$formatted_output" >> "$AUDITCONFIG"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-86674-9
  - CJIS-5.4.1.1
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - auditd_data_retention_max_log_file_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_auditd_max_log_file_action # promote to variable
  set_fact:
    var_auditd_max_log_file_action: !!str keep_logs
  tags:
    - always

- name: Configure auditd max_log_file_action Upon Reaching Maximum Log Size
  lineinfile:
    dest: /etc/audit/auditd.conf
    line: max_log_file_action = {{ var_auditd_max_log_file_action }}
    regexp: ^\s*max_log_file_action\s*=\s*.*$
    state: present
    create: true
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-86674-9
  - CJIS-5.4.1.1
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - auditd_data_retention_max_log_file_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,{{ %23%0A%23%20This%20file%20controls%20the%20configuration%20of%20the%20audit%20daemon%0A%23%0A%0Alocal_events%20%3D%20yes%0Awrite_logs%20%3D%20yes%0Alog_file%20%3D%20/var/log/audit/audit.log%0Alog_group%20%3D%20root%0Alog_format%20%3D%20ENRICHED%0Aflush%20%3D%20%7B%7B.var_auditd_flush%7D%7D%0Afreq%20%3D%2050%0Amax_log_file%20%3D%20%7B%7B.var_auditd_max_log_file%7D%7D%0Anum_logs%20%3D%20%7B%7B.var_auditd_num_logs%7D%7D%0Apriority_boost%20%3D%204%0Aname_format%20%3D%20hostname%0A%23%23name%20%3D%20mydomain%0Amax_log_file_action%20%3D%20%7B%7B.var_auditd_max_log_file_action%7D%7D%0Aspace_left%20%3D%20%7B%7B.var_auditd_space_left%7D%7D%0Aspace_left_action%20%3D%20%7B%7B.var_auditd_space_left_action%7D%7D%0Averify_email%20%3D%20yes%0Aaction_mail_acct%20%3D%20%7B%7B.var_auditd_action_mail_acct%7D%7D%0Aadmin_space_left%20%3D%2050%0Aadmin_space_left_action%20%3D%20syslog%0Adisk_full_action%20%3D%20%7B%7B.var_auditd_disk_full_action%7D%7D%0Adisk_error_action%20%3D%20%7B%7B.var_auditd_disk_error_action%7D%7D%0Ause_libwrap%20%3D%20yes%0A%23%23tcp_listen_port%20%3D%2060%0Atcp_listen_queue%20%3D%205%0Atcp_max_per_addr%20%3D%201%0A%23%23tcp_client_ports%20%3D%201024-65535%0Atcp_client_max_idle%20%3D%200%0Atransport%20%3D%20TCP%0Akrb5_principal%20%3D%20auditd%0A%23%23krb5_key_file%20%3D%20/etc/audit/audit.key%0Adistribute_network%20%3D%20no%0Aq_depth%20%3D%20400%0Aoverflow_action%20%3D%20syslog%0Amax_restarts%20%3D%2010%0Aplugin_dir%20%3D%20/etc/audit/plugins.d }}
        mode: 0640
        path: /etc/audit/auditd.conf
        overwrite: true
OVAL test results details

admin space left action   oval:ssg-test_auditd_data_retention_max_log_file_action:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/audit/auditd.confmax_log_file_action = ROTATE
Configure auditd space_left Action on Low Disk Spacexccdf_org.ssgproject.content_rule_auditd_data_retention_space_left_action mediumCCE-88897-4

Configure auditd space_left Action on Low Disk Space

Rule IDxccdf_org.ssgproject.content_rule_auditd_data_retention_space_left_action
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-auditd_data_retention_space_left_action:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-88897-4

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01
cui3.3.1
hipaa164.312(a)(2)(ii)
isa-62443-20094.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2
iso27001-2013A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1
nistAU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a)
nist-csfDE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4
pcidssReq-10.7
os-srgSRG-OS-000343-GPOS-00134
cis6.3.2.4
pcidss410.5.1, 10.5
Description
The auditd service can be configured to take an action when disk space starts to run low. Edit the file /etc/audit/auditd.conf. Modify the following line, substituting ACTION appropriately:
space_left_action = ACTION
       
Possible values for ACTION are described in the auditd.conf man page. These include:
  • syslog
  • email
  • exec
  • suspend
  • single
  • halt
Set this to email (instead of the default, which is suspend) as it is more likely to get prompt attention. Acceptable values also include suspend, single, and halt.
Rationale
Notifying administrators of an impending disk space problem may allow them to take corrective action prior to any disruption.

# Remediation is applicable only in certain platforms
if rpm --quiet -q audit && rpm --quiet -q kernel; then

var_auditd_space_left_action='email|exec|single|halt'


var_auditd_space_left_action="$(echo $var_auditd_space_left_action | cut -d \| -f 1)"
#
# If space_left_action present in /etc/audit/auditd.conf, change value
# to var_auditd_space_left_action, else
# add "space_left_action = $var_auditd_space_left_action" to /etc/audit/auditd.conf
#

AUDITCONFIG=/etc/audit/auditd.conf

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^space_left_action")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_space_left_action"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^space_left_action\\>" "$AUDITCONFIG"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^space_left_action\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG"
else
    if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG"
    fi
    cce="CCE-88897-4"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "$AUDITCONFIG" >> "$AUDITCONFIG"
    printf '%s\n' "$formatted_output" >> "$AUDITCONFIG"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88897-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - PCI-DSSv4-10.5
  - PCI-DSSv4-10.5.1
  - auditd_data_retention_space_left_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_auditd_space_left_action # promote to variable
  set_fact:
    var_auditd_space_left_action: !!str email|exec|single|halt
  tags:
    - always

- name: Configure auditd space_left Action on Low Disk Space
  lineinfile:
    dest: /etc/audit/auditd.conf
    line: space_left_action = {{ var_auditd_space_left_action.split('|')[0] }}
    regexp: ^\s*space_left_action\s*=\s*.*$
    state: present
    create: true
  when:
  - '"audit" in ansible_facts.packages'
  - '"kernel" in ansible_facts.packages'
  tags:
  - CCE-88897-4
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-53-AU-5(1)
  - NIST-800-53-AU-5(2)
  - NIST-800-53-AU-5(4)
  - NIST-800-53-AU-5(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - PCI-DSSv4-10.5
  - PCI-DSSv4-10.5.1
  - auditd_data_retention_space_left_action
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,{{ %23%0A%23%20This%20file%20controls%20the%20configuration%20of%20the%20audit%20daemon%0A%23%0A%0Alocal_events%20%3D%20yes%0Awrite_logs%20%3D%20yes%0Alog_file%20%3D%20/var/log/audit/audit.log%0Alog_group%20%3D%20root%0Alog_format%20%3D%20ENRICHED%0Aflush%20%3D%20%7B%7B.var_auditd_flush%7D%7D%0Afreq%20%3D%2050%0Amax_log_file%20%3D%20%7B%7B.var_auditd_max_log_file%7D%7D%0Anum_logs%20%3D%20%7B%7B.var_auditd_num_logs%7D%7D%0Apriority_boost%20%3D%204%0Aname_format%20%3D%20hostname%0A%23%23name%20%3D%20mydomain%0Amax_log_file_action%20%3D%20%7B%7B.var_auditd_max_log_file_action%7D%7D%0Aspace_left%20%3D%20%7B%7B.var_auditd_space_left%7D%7D%0Aspace_left_action%20%3D%20%7B%7B.var_auditd_space_left_action%7D%7D%0Averify_email%20%3D%20yes%0Aaction_mail_acct%20%3D%20%7B%7B.var_auditd_action_mail_acct%7D%7D%0Aadmin_space_left%20%3D%2050%0Aadmin_space_left_action%20%3D%20syslog%0Adisk_full_action%20%3D%20%7B%7B.var_auditd_disk_full_action%7D%7D%0Adisk_error_action%20%3D%20%7B%7B.var_auditd_disk_error_action%7D%7D%0Ause_libwrap%20%3D%20yes%0A%23%23tcp_listen_port%20%3D%2060%0Atcp_listen_queue%20%3D%205%0Atcp_max_per_addr%20%3D%201%0A%23%23tcp_client_ports%20%3D%201024-65535%0Atcp_client_max_idle%20%3D%200%0Atransport%20%3D%20TCP%0Akrb5_principal%20%3D%20auditd%0A%23%23krb5_key_file%20%3D%20/etc/audit/audit.key%0Adistribute_network%20%3D%20no%0Aq_depth%20%3D%20400%0Aoverflow_action%20%3D%20syslog%0Amax_restarts%20%3D%2010%0Aplugin_dir%20%3D%20/etc/audit/plugins.d }}
        mode: 0640
        path: /etc/audit/auditd.conf
        overwrite: true
OVAL test results details

space left action  oval:ssg-test_auditd_data_retention_space_left_action:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/audit/auditd.confspace_left_action = SYSLOG
Verify that audit tools are owned by group rootxccdf_org.ssgproject.content_rule_file_groupownership_audit_binaries mediumCCE-87965-0

Verify that audit tools are owned by group root

Rule IDxccdf_org.ssgproject.content_rule_file_groupownership_audit_binaries
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_groupownership_audit_binaries:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-87965-0

References:
os-srgSRG-OS-000256-GPOS-00097, SRG-OS-000257-GPOS-00098
cis6.3.4.10
Description
The Red Hat Enterprise Linux 10 operating system audit tools must have the proper ownership configured to protected against unauthorized access. Verify it by running the following command:
$ stat -c "%n %G" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/audispd /sbin/augenrules

/sbin/auditctl root
/sbin/aureport root
/sbin/ausearch root

/sbin/auditd root

/sbin/augenrules root
/sbin/audisp-syslog root
Audit tools needed to successfully view and manipulate audit information system activity and records. Audit tools include custom queries and report generators
Rationale
Protecting audit information also includes identifying and protecting the tools used to view and manipulate log data. Therefore, protecting audit tools is necessary to prevent unauthorized operation on audit information. Operating systems providing tools to interface with audit information will leverage user permissions and roles identifying the user accessing the tools and the corresponding rights the user enjoys to make access decisions regarding the access to audit tools.
OVAL test results details

Testing group ownership of /sbin/auditctl  oval:ssg-test_file_groupownership_audit_binaries_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_audit_binaries_0:obj:1 of type file_object
FilepathFilterFilter
/sbin/auditctloval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_audit_binaries_0_0:ste:1

Testing group ownership of /sbin/aureport  oval:ssg-test_file_groupownership_audit_binaries_1:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_audit_binaries_1:obj:1 of type file_object
FilepathFilterFilter
/sbin/aureportoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_audit_binaries_0_0:ste:1

Testing group ownership of /sbin/ausearch  oval:ssg-test_file_groupownership_audit_binaries_2:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_audit_binaries_2:obj:1 of type file_object
FilepathFilterFilter
/sbin/ausearchoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_audit_binaries_0_0:ste:1

Testing group ownership of /sbin/auditd  oval:ssg-test_file_groupownership_audit_binaries_3:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_audit_binaries_3:obj:1 of type file_object
FilepathFilterFilter
/sbin/auditdoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_audit_binaries_0_0:ste:1

Testing group ownership of /sbin/augenrules  oval:ssg-test_file_groupownership_audit_binaries_4:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_audit_binaries_4:obj:1 of type file_object
FilepathFilterFilter
/sbin/augenrulesoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_audit_binaries_0_0:ste:1

Testing group ownership of /sbin/audisp-syslog  oval:ssg-test_file_groupownership_audit_binaries_5:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_groupownership_audit_binaries_5:obj:1 of type file_object
FilepathFilterFilter
/sbin/audisp-syslogoval:ssg-symlink_file_groupowner:ste:1oval:ssg-state_file_groupownership_audit_binaries_0_0:ste:1
Verify that audit tools are owned by rootxccdf_org.ssgproject.content_rule_file_ownership_audit_binaries mediumCCE-89400-6

Verify that audit tools are owned by root

Rule IDxccdf_org.ssgproject.content_rule_file_ownership_audit_binaries
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_ownership_audit_binaries:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-89400-6

References:
os-srgSRG-OS-000256-GPOS-00097, SRG-OS-000257-GPOS-00098
cis6.3.4.9
Description
The Red Hat Enterprise Linux 10 operating system audit tools must have the proper ownership configured to protected against unauthorized access. Verify it by running the following command:
$ stat -c "%n %U" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/audispd /sbin/augenrules /sbin/audisp-syslog

/sbin/auditctl root
/sbin/aureport root
/sbin/ausearch root

/sbin/auditd root

/sbin/augenrules root
/sbin/audisp-syslog root
Audit tools needed to successfully view and manipulate audit information system activity and records. Audit tools include custom queries and report generators
Rationale
Protecting audit information also includes identifying and protecting the tools used to view and manipulate log data. Therefore, protecting audit tools is necessary to prevent unauthorized operation on audit information. Operating systems providing tools to interface with audit information will leverage user permissions and roles identifying the user accessing the tools and the corresponding rights the user enjoys to make access decisions regarding the access to audit tools.
OVAL test results details

Testing user ownership of /sbin/auditctl  oval:ssg-test_file_ownership_audit_binaries_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_audit_binaries_0:obj:1 of type file_object
FilepathFilterFilter
/sbin/auditctloval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_audit_binaries_0_0:ste:1

Testing user ownership of /sbin/aureport  oval:ssg-test_file_ownership_audit_binaries_1:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_audit_binaries_1:obj:1 of type file_object
FilepathFilterFilter
/sbin/aureportoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_audit_binaries_0_0:ste:1

Testing user ownership of /sbin/ausearch  oval:ssg-test_file_ownership_audit_binaries_2:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_audit_binaries_2:obj:1 of type file_object
FilepathFilterFilter
/sbin/ausearchoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_audit_binaries_0_0:ste:1

Testing user ownership of /sbin/auditd  oval:ssg-test_file_ownership_audit_binaries_3:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_audit_binaries_3:obj:1 of type file_object
FilepathFilterFilter
/sbin/auditdoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_audit_binaries_0_0:ste:1

Testing user ownership of /sbin/augenrules  oval:ssg-test_file_ownership_audit_binaries_4:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_audit_binaries_4:obj:1 of type file_object
FilepathFilterFilter
/sbin/augenrulesoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_audit_binaries_0_0:ste:1

Testing user ownership of /sbin/audisp-syslog  oval:ssg-test_file_ownership_audit_binaries_5:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_ownership_audit_binaries_5:obj:1 of type file_object
FilepathFilterFilter
/sbin/audisp-syslogoval:ssg-symlink_file_owner:ste:1oval:ssg-state_file_ownership_audit_binaries_0_0:ste:1
Verify that audit tools Have Mode 0755 or lessxccdf_org.ssgproject.content_rule_file_permissions_audit_binaries mediumCCE-86166-6

Verify that audit tools Have Mode 0755 or less

Rule IDxccdf_org.ssgproject.content_rule_file_permissions_audit_binaries
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-file_permissions_audit_binaries:def:1
Time2025-07-22T08:32:07+00:00
Severitymedium
Identifiers:

CCE-86166-6

References:
os-srgSRG-OS-000256-GPOS-00097, SRG-OS-000257-GPOS-00098
cis6.3.4.8
Description
The Red Hat Enterprise Linux 10 operating system audit tools must have the proper permissions configured to protected against unauthorized access. Verify it by running the following command:
$ stat -c "%n %a" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/audispd /sbin/augenrules

/sbin/auditctl 755
/sbin/aureport 755
/sbin/ausearch 755
/sbin/auditd 755
/sbin/augenrules 755
/sbin/audisp-syslog 755
Audit tools needed to successfully view and manipulate audit information system activity and records. Audit tools include custom queries and report generators
Rationale
Protecting audit information also includes identifying and protecting the tools used to view and manipulate log data. Therefore, protecting audit tools is necessary to prevent unauthorized operation on audit information. Operating systems providing tools to interface with audit information will leverage user permissions and roles identifying the user accessing the tools and the corresponding rights the user enjoys to make access decisions regarding the access to audit tools.
OVAL test results details

Testing mode of /sbin/auditctl  oval:ssg-test_file_permissions_audit_binaries_0:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_audit_binaries_0:obj:1 of type file_object
FilepathFilterFilter
/sbin/auditctloval:ssg-exclude_symlinks__audit_binaries:ste:1oval:ssg-state_file_permissions_audit_binaries_0_mode_0755or_stricter_:ste:1

Testing mode of /sbin/aureport  oval:ssg-test_file_permissions_audit_binaries_1:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_audit_binaries_1:obj:1 of type file_object
FilepathFilterFilter
/sbin/aureportoval:ssg-exclude_symlinks__audit_binaries:ste:1oval:ssg-state_file_permissions_audit_binaries_1_mode_0755or_stricter_:ste:1

Testing mode of /sbin/ausearch  oval:ssg-test_file_permissions_audit_binaries_2:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_audit_binaries_2:obj:1 of type file_object
FilepathFilterFilter
/sbin/ausearchoval:ssg-exclude_symlinks__audit_binaries:ste:1oval:ssg-state_file_permissions_audit_binaries_2_mode_0755or_stricter_:ste:1

Testing mode of /sbin/auditd  oval:ssg-test_file_permissions_audit_binaries_3:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_audit_binaries_3:obj:1 of type file_object
FilepathFilterFilter
/sbin/auditdoval:ssg-exclude_symlinks__audit_binaries:ste:1oval:ssg-state_file_permissions_audit_binaries_3_mode_0755or_stricter_:ste:1

Testing mode of /sbin/augenrules  oval:ssg-test_file_permissions_audit_binaries_4:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_audit_binaries_4:obj:1 of type file_object
FilepathFilterFilter
/sbin/augenrulesoval:ssg-exclude_symlinks__audit_binaries:ste:1oval:ssg-state_file_permissions_audit_binaries_4_mode_0755or_stricter_:ste:1

Testing mode of /sbin/audisp-syslog  oval:ssg-test_file_permissions_audit_binaries_5:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-object_file_permissions_audit_binaries_5:obj:1 of type file_object
FilepathFilterFilter
/sbin/audisp-syslogoval:ssg-exclude_symlinks__audit_binaries:ste:1oval:ssg-state_file_permissions_audit_binaries_5_mode_0755or_stricter_:ste:1
Ensure the audit-libs package as a part of audit Subsystem is Installedxccdf_org.ssgproject.content_rule_package_audit-libs_installed mediumCCE-90611-5

Ensure the audit-libs package as a part of audit Subsystem is Installed

Rule IDxccdf_org.ssgproject.content_rule_package_audit-libs_installed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_audit-libs_installed:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-90611-5

References:
nerc-cipCIP-004-6 R3.3, CIP-007-3 R6.5
nistAC-7(a), AU-7(1), AU-7(2), AU-14, AU-12(2), AU-2(a), CM-6(a)
pcidssReq-10.2.1
os-srgSRG-OS-000062-GPOS-00031, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000337-GPOS-00129, SRG-OS-000348-GPOS-00136, SRG-OS-000349-GPOS-00137, SRG-OS-000350-GPOS-00138, SRG-OS-000351-GPOS-00139, SRG-OS-000352-GPOS-00140, SRG-OS-000353-GPOS-00141, SRG-OS-000354-GPOS-00142, SRG-OS-000358-GPOS-00145, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000475-GPOS-00220
cis6.3.1.1
Description
The audit-libs package should be installed.
Rationale
The auditd service is an access monitoring and accounting daemon, watching system calls to audit any access, in comparison with potential local access control policy such as SELinux policy.
OVAL test results details

package audit-libs is installed  oval:ssg-test_package_audit-libs_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedaudit-libsx86_64(none)1.el104.0.30:4.0.3-1.el10199e2f91fd431d51audit-libs-0:4.0.3-1.el10.x86_64
Ensure the audit Subsystem is Installedxccdf_org.ssgproject.content_rule_package_audit_installed mediumCCE-88240-7

Ensure the audit Subsystem is Installed

Rule IDxccdf_org.ssgproject.content_rule_package_audit_installed
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-package_audit_installed:def:1
Time2025-07-22T08:32:00+00:00
Severitymedium
Identifiers:

CCE-88240-7

References:
hipaa164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b)
nerc-cipCIP-004-6 R3.3, CIP-007-3 R6.5
nistAC-7(a), AU-7(1), AU-7(2), AU-14, AU-12(2), AU-2(a), CM-6(a)
osppFAU_GEN.1
pcidssReq-10.1
os-srgSRG-OS-000062-GPOS-00031, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000337-GPOS-00129, SRG-OS-000348-GPOS-00136, SRG-OS-000349-GPOS-00137, SRG-OS-000350-GPOS-00138, SRG-OS-000351-GPOS-00139, SRG-OS-000352-GPOS-00140, SRG-OS-000353-GPOS-00141, SRG-OS-000354-GPOS-00142, SRG-OS-000358-GPOS-00145, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000475-GPOS-00220
anssiR33, R73
cis6.3.1.1
pcidss410.2.1, 10.2
Description
The audit package should be installed.
Rationale
The auditd service is an access monitoring and accounting daemon, watching system calls to audit any access, in comparison with potential local access control policy such as SELinux policy.
OVAL test results details

package audit is installed  oval:ssg-test_package_audit_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedauditx86_64(none)1.el104.0.30:4.0.3-1.el10199e2f91fd431d51audit-0:4.0.3-1.el10.x86_64
Enable auditd Servicexccdf_org.ssgproject.content_rule_service_auditd_enabled mediumCCE-87955-1

Enable auditd Service

Rule IDxccdf_org.ssgproject.content_rule_service_auditd_enabled
Result
pass
Multi-check ruleno
OVAL Definition IDoval:ssg-service_auditd_enabled:def:1
Time2025-07-22T08:32:02+00:00
Severitymedium
Identifiers:

CCE-87955-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.3.1, 3.3.2, 3.3.6
hipaa164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nerc-cipCIP-004-6 R3.3, CIP-007-3 R6.5
nistAC-2(g), AU-3, AU-10, AU-2(d), AU-12(c), AU-14(1), AC-6(9), CM-6(a), SI-4(23)
nist-csfDE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
osppFAU_GEN.1
pcidssReq-10.1
os-srgSRG-OS-000062-GPOS-00031, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000337-GPOS-00129, SRG-OS-000348-GPOS-00136, SRG-OS-000349-GPOS-00137, SRG-OS-000350-GPOS-00138, SRG-OS-000351-GPOS-00139, SRG-OS-000352-GPOS-00140, SRG-OS-000353-GPOS-00141, SRG-OS-000354-GPOS-00142, SRG-OS-000358-GPOS-00145, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000475-GPOS-00220
app-srg-ctrSRG-APP-000095-CTR-000170, SRG-APP-000409-CTR-000990, SRG-APP-000508-CTR-001300, SRG-APP-000510-CTR-001310
anssiR33, R73
cis6.3.1.4
pcidss410.2.1, 10.2
Description
The auditd service is an essential userspace component of the Linux Auditing System, as it is responsible for writing audit records to disk. The auditd service can be enabled with the following command:
$ sudo systemctl enable auditd.service
Rationale
Without establishing what type of events occurred, it would be difficult to establish, correlate, and investigate the events leading up to an outage or attack. Ensuring the auditd service is active ensures audit records generated by the kernel are appropriately recorded.

Additionally, a properly configured audit subsystem ensures that actions of individual system users can be uniquely traced to those users so they can be held accountable for their actions.
OVAL test results details

package audit is installed  oval:ssg-test_service_auditd_package_audit_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedauditx86_64(none)1.el104.0.30:4.0.3-1.el10199e2f91fd431d51audit-0:4.0.3-1.el10.x86_64

Test that the auditd service is running  oval:ssg-test_service_running_auditd:tst:1  true

Following items have been found on the system:
Result of item-state comparisonUnitPropertyValue
trueauditd.serviceActiveStateactive

systemd test  oval:ssg-test_multi_user_wants_auditd:tst:1  true

Following items have been found on the system:
Result of item-state comparisonUnitDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependency
truemulti-user.targetbasic.targetsysinit.targetlvm2-lvmpolld.socketswap.targetdev-mqueue.mountsystemd-update-utmp.servicesys-kernel-tracing.mountsystemd-hibernate-clear.servicesystemd-udevd.servicesystemd-update-done.servicesystemd-journal-catalog-update.servicesystemd-ask-password-console.pathsystemd-boot-random-seed.servicesystemd-network-generator.serviceintegritysetup.targetsystemd-confext.servicesystemd-firstboot.servicesystemd-tpm2-setup.servicelvm2-monitor.servicesystemd-tmpfiles-setup-dev-early.servicesystemd-sysusers.serviceproc-sys-fs-binfmt_misc.automountldconfig.servicesystemd-machine-id-commit.servicesystemd-repart.servicecryptsetup.targetfips-crypto-policy-overlay.servicesystemd-pstore.serviceselinux-autorelabel-mark.servicesystemd-random-seed.servicedracut-shutdown.servicesystemd-sysext.servicesys-kernel-debug.mountsystemd-journald.servicedev-hugepages.mountveritysetup.targetsys-fs-fuse-connections.mountsystemd-journal-flush.servicesystemd-tmpfiles-setup.servicesystemd-modules-load.servicelocal-fs.target-.mountboot-efi.mountsystemd-remount-fs.servicesystemd-udev-trigger.servicesystemd-tpm2-setup-early.servicesystemd-pcrphase-sysinit.servicesystemd-sysctl.servicekmod-static-nodes.servicesystemd-pcrphase.servicesystemd-hwdb-update.servicesystemd-tmpfiles-setup-dev.servicesystemd-binfmt.servicesys-kernel-config.mountsystemd-pcrmachine.servicetimers.targetfstrim.timerdnf-makecache.timerlogrotate.timernm-cloud-setup.timerfwupd-refresh.timersystemd-tmpfiles-clean.timerraid-check.timerslices.target-.slicesystem.slicepaths.targetsockets.targetsystemd-coredump.socketsystemd-userdbd.socketsystemd-journald.socketsystemd-pcrextend.socketsshd-unix-local.socketsystemd-creds.socketsystemd-pcrlock.socketpcscd.socketsystemd-sysext.socketsystemd-udevd-kernel.socketsssd-kcm.socketdbus.socketsshd-vsock.socketsystemd-hostnamed.socketsystemd-bootctl.socketsystemd-udevd-control.socketsystemd-journald-dev-log.socketdm-event.socketsystemd-initctl.socketsystemd-logind.serviceinsights-unregister.pathsshd.servicecloud-init.targetcloud-config.servicecloud-init.servicecloud-final.servicecloud-init-local.servicesssd.serviceaudit-rules.servicerhcd.pathremote-fs.targetinsights-client-boot.servicerhsmcertd.serviceinsights-unregistered.pathsystemd-user-sessions.servicetuned.serviceremote-cryptsetup.targetinsights-register.pathrhcd-stop.pathirqbalance.servicesystemd-ask-password-wall.pathrsyslog.servicegetty.targetgetty@tty1.serviceserial-getty@ttyS0.servicecrond.servicekdump.serviceauditd.serviceNetworkManager.servicemdmonitor.servicesystemd-update-utmp-runlevel.servicechronyd.service

systemd test  oval:ssg-test_multi_user_wants_auditd_socket:tst:1  false

Following items have been found on the system:
Result of item-state comparisonUnitDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependencyDependency
falsemulti-user.targetbasic.targetsysinit.targetlvm2-lvmpolld.socketswap.targetdev-mqueue.mountsystemd-update-utmp.servicesys-kernel-tracing.mountsystemd-hibernate-clear.servicesystemd-udevd.servicesystemd-update-done.servicesystemd-journal-catalog-update.servicesystemd-ask-password-console.pathsystemd-boot-random-seed.servicesystemd-network-generator.serviceintegritysetup.targetsystemd-confext.servicesystemd-firstboot.servicesystemd-tpm2-setup.servicelvm2-monitor.servicesystemd-tmpfiles-setup-dev-early.servicesystemd-sysusers.serviceproc-sys-fs-binfmt_misc.automountldconfig.servicesystemd-machine-id-commit.servicesystemd-repart.servicecryptsetup.targetfips-crypto-policy-overlay.servicesystemd-pstore.serviceselinux-autorelabel-mark.servicesystemd-random-seed.servicedracut-shutdown.servicesystemd-sysext.servicesys-kernel-debug.mountsystemd-journald.servicedev-hugepages.mountveritysetup.targetsys-fs-fuse-connections.mountsystemd-journal-flush.servicesystemd-tmpfiles-setup.servicesystemd-modules-load.servicelocal-fs.target-.mountboot-efi.mountsystemd-remount-fs.servicesystemd-udev-trigger.servicesystemd-tpm2-setup-early.servicesystemd-pcrphase-sysinit.servicesystemd-sysctl.servicekmod-static-nodes.servicesystemd-pcrphase.servicesystemd-hwdb-update.servicesystemd-tmpfiles-setup-dev.servicesystemd-binfmt.servicesys-kernel-config.mountsystemd-pcrmachine.servicetimers.targetfstrim.timerdnf-makecache.timerlogrotate.timernm-cloud-setup.timerfwupd-refresh.timersystemd-tmpfiles-clean.timerraid-check.timerslices.target-.slicesystem.slicepaths.targetsockets.targetsystemd-coredump.socketsystemd-userdbd.socketsystemd-journald.socketsystemd-pcrextend.socketsshd-unix-local.socketsystemd-creds.socketsystemd-pcrlock.socketpcscd.socketsystemd-sysext.socketsystemd-udevd-kernel.socketsssd-kcm.socketdbus.socketsshd-vsock.socketsystemd-hostnamed.socketsystemd-bootctl.socketsystemd-udevd-control.socketsystemd-journald-dev-log.socketdm-event.socketsystemd-initctl.socketsystemd-logind.serviceinsights-unregister.pathsshd.servicecloud-init.targetcloud-config.servicecloud-init.servicecloud-final.servicecloud-init-local.servicesssd.serviceaudit-rules.servicerhcd.pathremote-fs.targetinsights-client-boot.servicerhsmcertd.serviceinsights-unregistered.pathsystemd-user-sessions.servicetuned.serviceremote-cryptsetup.targetinsights-register.pathrhcd-stop.pathirqbalance.servicesystemd-ask-password-wall.pathrsyslog.servicegetty.targetgetty@tty1.serviceserial-getty@ttyS0.servicecrond.servicekdump.serviceauditd.serviceNetworkManager.servicemdmonitor.servicesystemd-update-utmp-runlevel.servicechronyd.service
Enable Auditing for Processes Which Start Prior to the Audit Daemonxccdf_org.ssgproject.content_rule_grub2_audit_argument lowCCE-88376-9

Enable Auditing for Processes Which Start Prior to the Audit Daemon

Rule IDxccdf_org.ssgproject.content_rule_grub2_audit_argument
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-grub2_audit_argument:def:1
Time2025-07-22T08:32:02+00:00
Severitylow
Identifiers:

CCE-88376-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 3, 4, 5, 6, 7, 8
cjis5.4.1.1
cobit5APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.02, DSS05.03, DSS05.04, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui3.3.1
hipaa164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nistAC-17(1), AU-14(1), AU-10, CM-6(a), IR-5(1)
nist-csfDE.AE-3, DE.AE-5, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
osppFAU_GEN.1
pcidssReq-10.3
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000473-GPOS-00218, SRG-OS-000254-GPOS-00095
cis6.3.1.2
pcidss410.7.2, 10.7
Description
To ensure all processes can be audited, even those which start prior to the audit daemon, add the argument audit=1 to the default GRUB 2 command line for the Linux operating system. To ensure that audit=1 is added as a kernel command line argument to newly installed kernels, add audit=1 to the default Grub2 command line for Linux operating systems. Modify the line within /etc/default/grub as shown below:
GRUB_CMDLINE_LINUX="... audit=1 ..."
Run the following command to update command line for already installed kernels:
# grubby --update-kernel=ALL --args="audit=1"
If the system is distributed as a bootable container image, GRUB2 can't be configured using the method described above, but the following method needs to be used instead. The kernel arguments should be set in /usr/lib/bootc/kargs.d in a TOML file that has the following form:
# /usr/lib/bootc/kargs.d/10-example.toml
kargs = ["audit=1"]
For more details on configuring kernel arguments in bootable container images, please refer to Bootc documentation.
Rationale
Each process on the system carries an "auditable" flag which indicates whether its activities can be audited. Although auditd takes care of enabling this for all processes which launch after it does, adding the kernel argument ensures it is set for every process during boot.

# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel && { rpm --quiet -q grub2-common; }; then

expected_value="1"


if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    KARGS_DIR="/usr/lib/bootc/kargs.d/"
    if grep -q -E "audit" "$KARGS_DIR/*.toml" ; then
        sed -i -E "s/^(\s*kargs\s*=\s*\[.*)\"audit=[^\"]*\"(.*]\s*)/\1\"audit=$expected_value\"\2/" "$KARGS_DIR/*.toml"
    else
        echo "kargs = [\"audit=$expected_value\"]" >> "$KARGS_DIR/10-audit.toml"
    fi
else

    grubby --update-kernel=ALL --args=audit=1

fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:medium
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88376-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-53-AC-17(1)
  - NIST-800-53-AU-10
  - NIST-800-53-AU-14(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IR-5(1)
  - PCI-DSS-Req-10.3
  - PCI-DSSv4-10.7
  - PCI-DSSv4-10.7.2
  - grub2_audit_argument
  - low_disruption
  - low_severity
  - medium_complexity
  - reboot_required
  - restrict_strategy

- name: Update grub defaults and the bootloader menu
  command: /sbin/grubby --update-kernel=ALL --args="audit=1"
  when:
  - '"kernel" in ansible_facts.packages'
  - '"grub2-common" in ansible_facts.packages'
  tags:
  - CCE-88376-9
  - CJIS-5.4.1.1
  - NIST-800-171-3.3.1
  - NIST-800-53-AC-17(1)
  - NIST-800-53-AU-10
  - NIST-800-53-AU-14(1)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IR-5(1)
  - PCI-DSS-Req-10.3
  - PCI-DSSv4-10.7
  - PCI-DSSv4-10.7.2
  - grub2_audit_argument
  - low_disruption
  - low_severity
  - medium_complexity
  - reboot_required
  - restrict_strategy

[customizations.kernel]
append = "audit=1"

Complexity:medium
Disruption:low
Reboot:true
Strategy:restrict

bootloader audit=1
OVAL test results details

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

check kernel command line parameters for audit=1 for all boot entries.  oval:ssg-test_grub2_audit_entries:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/boot/loader/entries/ffffffffffffffffffffffffffffffff-6.12.0-55.18.1.el10_0.x86_64.confoptions root=UUID=f6af1ffc-108f-4dc4-94b7-7142b712ad43 console=tty0 console=ttyS0,115200n8 nvme_core.io_timeout=4294967295 crashkernel=2G-64G:256M,64G-:512M $tuned_params
false/boot/loader/entries/ec217f1e9df41a99ab5ef602b51e88a5-6.12.0-55.22.1.el10_0.x86_64.confoptions root=UUID=f6af1ffc-108f-4dc4-94b7-7142b712ad43 console=tty0 console=ttyS0,115200n8 nvme_core.io_timeout=4294967295 crashkernel=2G-64G:256M,64G-:512M $tuned_params

check for audit=1 in /etc/default/grub via GRUB_CMDLINE_LINUX  oval:ssg-test_grub2_audit_argument:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/default/grubGRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8 nvme_core.io_timeout=4294967295"

check for audit=1 in /etc/default/grub via GRUB_CMDLINE_LINUX_DEFAULT  oval:ssg-test_grub2_audit_argument_default:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_grub2_audit_argument_default:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/default/grub^\s*GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$1

Check for GRUB_DISABLE_RECOVERY=true in /etc/default/grub  oval:ssg-test_bootloader_disable_recovery_set_to_true:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_bootloader_disable_recovery_argument:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/default/grub^\s*GRUB_DISABLE_RECOVERY=(.*)$1

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

check kernel command line parameters for audit=1 for all boot entries.  oval:ssg-test_grub2_audit_usr_lib_bootc_kargs_d:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_grub2_audit_usr_lib_bootc_kargs_d:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/bootc/kargs.d/^.*\.toml$^kargs = \[([^\]]+)\]$1
Extend Audit Backlog Limit for the Audit Daemonxccdf_org.ssgproject.content_rule_grub2_audit_backlog_limit_argument lowCCE-88192-0

Extend Audit Backlog Limit for the Audit Daemon

Rule IDxccdf_org.ssgproject.content_rule_grub2_audit_backlog_limit_argument
Result
fail
Multi-check ruleno
OVAL Definition IDoval:ssg-grub2_audit_backlog_limit_argument:def:1
Time2025-07-22T08:32:02+00:00
Severitylow
Identifiers:

CCE-88192-0

References:
nistCM-6(a)
osppFAU_STG.1, FAU_STG.3
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000254-GPOS-00095, SRG-OS-000341-GPOS-00132, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215
cis6.3.1.3
pcidss410.7.2, 10.7
Description
To improve the kernel capacity to queue all log events, even those which occurred prior to the audit daemon, add the argument audit_backlog_limit=8192 to the default GRUB 2 command line for the Linux operating system. To ensure that audit_backlog_limit=8192 is added as a kernel command line argument to newly installed kernels, add audit_backlog_limit=8192 to the default Grub2 command line for Linux operating systems. Modify the line within /etc/default/grub as shown below:
GRUB_CMDLINE_LINUX="... audit_backlog_limit=8192 ..."
Run the following command to update command line for already installed kernels:
# grubby --update-kernel=ALL --args="audit_backlog_limit=8192"
If the system is distributed as a bootable container image, GRUB2 can't be configured using the method described above, but the following method needs to be used instead. The kernel arguments should be set in /usr/lib/bootc/kargs.d in a TOML file that has the following form:
# /usr/lib/bootc/kargs.d/10-example.toml
kargs = ["audit_backlog_limit=8192"]
For more details on configuring kernel arguments in bootable container images, please refer to Bootc documentation.
Rationale
audit_backlog_limit sets the queue length for audit events awaiting transfer to the audit daemon. Until the audit daemon is up and running, all log messages are stored in this queue. If the queue is overrun during boot process, the action defined by audit failure flag is taken.

# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel && { rpm --quiet -q grub2-common; }; then

expected_value="8192"


if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
    KARGS_DIR="/usr/lib/bootc/kargs.d/"
    if grep -q -E "audit_backlog_limit" "$KARGS_DIR/*.toml" ; then
        sed -i -E "s/^(\s*kargs\s*=\s*\[.*)\"audit_backlog_limit=[^\"]*\"(.*]\s*)/\1\"audit_backlog_limit=$expected_value\"\2/" "$KARGS_DIR/*.toml"
    else
        echo "kargs = [\"audit_backlog_limit=$expected_value\"]" >> "$KARGS_DIR/10-audit_backlog_limit.toml"
    fi
else

    grubby --update-kernel=ALL --args=audit_backlog_limit=8192

fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Complexity:medium
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-88192-0
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-10.7
  - PCI-DSSv4-10.7.2
  - grub2_audit_backlog_limit_argument
  - low_disruption
  - low_severity
  - medium_complexity
  - reboot_required
  - restrict_strategy

- name: Update grub defaults and the bootloader menu
  command: /sbin/grubby --update-kernel=ALL --args="audit_backlog_limit=8192"
  when:
  - '"kernel" in ansible_facts.packages'
  - '"grub2-common" in ansible_facts.packages'
  tags:
  - CCE-88192-0
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-10.7
  - PCI-DSSv4-10.7.2
  - grub2_audit_backlog_limit_argument
  - low_disruption
  - low_severity
  - medium_complexity
  - reboot_required
  - restrict_strategy

[customizations.kernel]
append = "audit_backlog_limit=8192"

Complexity:medium
Disruption:low
Reboot:true
Strategy:restrict

bootloader audit_backlog_limit=8192
OVAL test results details

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

check kernel command line parameters for audit_backlog_limit=8192 for all boot entries.  oval:ssg-test_grub2_audit_backlog_limit_entries:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/boot/loader/entries/ffffffffffffffffffffffffffffffff-6.12.0-55.18.1.el10_0.x86_64.confoptions root=UUID=f6af1ffc-108f-4dc4-94b7-7142b712ad43 console=tty0 console=ttyS0,115200n8 nvme_core.io_timeout=4294967295 crashkernel=2G-64G:256M,64G-:512M $tuned_params
false/boot/loader/entries/ec217f1e9df41a99ab5ef602b51e88a5-6.12.0-55.22.1.el10_0.x86_64.confoptions root=UUID=f6af1ffc-108f-4dc4-94b7-7142b712ad43 console=tty0 console=ttyS0,115200n8 nvme_core.io_timeout=4294967295 crashkernel=2G-64G:256M,64G-:512M $tuned_params

check for audit_backlog_limit=8192 in /etc/default/grub via GRUB_CMDLINE_LINUX  oval:ssg-test_grub2_audit_backlog_limit_argument:tst:1  false

Following items have been found on the system:
Result of item-state comparisonPathContent
false/etc/default/grubGRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8 nvme_core.io_timeout=4294967295"

check for audit_backlog_limit=8192 in /etc/default/grub via GRUB_CMDLINE_LINUX_DEFAULT  oval:ssg-test_grub2_audit_backlog_limit_argument_default:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_grub2_audit_backlog_limit_argument_default:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/default/grub^\s*GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$1

Check for GRUB_DISABLE_RECOVERY=true in /etc/default/grub  oval:ssg-test_bootloader_disable_recovery_set_to_true:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_bootloader_disable_recovery_argument:obj:1 of type textfilecontent54_object
FilepathPatternInstance
/etc/default/grub^\s*GRUB_DISABLE_RECOVERY=(.*)$1

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64

package kernel is installed  oval:ssg-bootc_platform_test_kernel_installed:tst:1  true

Following items have been found on the system:
Result of item-state comparisonNameArchEpochReleaseVersionEvrSignature keyidExtended name
not evaluatedkernelx86_64(none)55.22.1.el10_06.12.00:6.12.0-55.22.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.22.1.el10_0.x86_64
not evaluatedkernelx86_64(none)55.18.1.el10_06.12.00:6.12.0-55.18.1.el10_0199e2f91fd431d51kernel-0:6.12.0-55.18.1.el10_0.x86_64

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package rpm-ostree is installed  oval:ssg-bootc_platform_test_rpm_ostree_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_rpm_ostree_installed:obj:1 of type rpminfo_object
Name
rpm-ostree

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package bootc is installed  oval:ssg-bootc_platform_test_bootc_installed:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_bootc_installed:obj:1 of type rpminfo_object
Name
bootc

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

package openshift-kubelet is removed  oval:ssg-bootc_platform_test_openshift_kubelet_removed:tst:1  true

No items have been found conforming to the following objects:
Object oval:ssg-obj_bootc_platform_test_openshift_kubelet_removed:obj:1 of type rpminfo_object
Name
openshift-kubelet

check kernel command line parameters for audit_backlog_limit=8192 for all boot entries.  oval:ssg-test_grub2_audit_backlog_limit_usr_lib_bootc_kargs_d:tst:1  false

No items have been found conforming to the following objects:
Object oval:ssg-object_grub2_audit_backlog_limit_usr_lib_bootc_kargs_d:obj:1 of type textfilecontent54_object
PathFilenamePatternInstance
/usr/lib/bootc/kargs.d/^.*\.toml$^kargs = \[([^\]]+)\]$1
Scroll back to the first rule
Red Hat and Red Hat Enterprise Linux are either registered trademarks or trademarks of Red Hat, Inc. in the United States and other countries. All other names are registered trademarks or trademarks of their respective companies.